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

This commit is contained in:
Wojtek Figat
2023-11-02 13:47:49 +01:00
102 changed files with 1459 additions and 938 deletions

1
.gitignore vendored
View File

@@ -12,6 +12,7 @@ Source/*.csproj
/Package_*/ /Package_*/
!Source/Engine/Debug !Source/Engine/Debug
/Source/Platforms/Editor/Linux/Mono/etc/mono/registry /Source/Platforms/Editor/Linux/Mono/etc/mono/registry
PackageEditor_Cert.command
PackageEditor_Cert.bat PackageEditor_Cert.bat
PackagePlatforms_Cert.bat PackagePlatforms_Cert.bat

BIN
Content/Editor/Particles/Smoke.flax (Stored with Git LFS)

Binary file not shown.

BIN
Content/Editor/Particles/Sparks.flax (Stored with Git LFS)

Binary file not shown.

View File

@@ -33,4 +33,3 @@ public class %class% : Script
// Here you can add code that needs to be called every frame // Here you can add code that needs to be called every frame
} }
} }

View File

@@ -3,6 +3,7 @@
"Version": { "Version": {
"Major": 1, "Major": 1,
"Minor": 8, "Minor": 8,
"Revision": 0,
"Build": 6500 "Build": 6500
}, },
"Company": "Flax", "Company": "Flax",

View File

@@ -15,7 +15,7 @@ if errorlevel 1 goto BuildToolFailed
:: Build bindings for all editor configurations :: Build bindings for all editor configurations
echo Building C# bindings... 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 popd
echo Done! echo Done!

View File

@@ -14,4 +14,4 @@ bash ./Development/Scripts/Mac/CallBuildTool.sh --genproject "$@"
# Build bindings for all editor configurations # Build bindings for all editor configurations
echo Building C# bindings... echo Building C# bindings...
# TODO: Detect the correct architecture here # 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

View File

@@ -14,4 +14,4 @@ bash ./Development/Scripts/Linux/CallBuildTool.sh --genproject "$@"
# Build bindings for all editor configurations # Build bindings for all editor configurations
echo Building C# bindings... echo Building C# bindings...
# TODO: Detect the correct architecture here # 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

View File

@@ -4,7 +4,7 @@
<a href="https://flaxengine.com/discord"><img src="https://discordapp.com/api/guilds/437989205315158016/widget.png"/></a> <a href="https://flaxengine.com/discord"><img src="https://discordapp.com/api/guilds/437989205315158016/widget.png"/></a>
Flax Engine is a high quality modern 3D game engine written in C++ and C#. 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. 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.

View File

@@ -104,4 +104,19 @@ bool LinuxPlatformTools::OnDeployBinaries(CookingData& data)
return false; return false;
} }
void LinuxPlatformTools::OnRun(CookingData& data, String& executableFile, String& commandLineFormat, String& workingDir)
{
// Pick the first executable file
Array<String> files;
FileSystem::DirectoryGetFiles(files, data.NativeCodeOutputPath, TEXT("*"), DirectorySearchOption::TopDirectoryOnly);
for (auto& file : files)
{
if (FileSystem::GetExtension(file).IsEmpty())
{
executableFile = file;
break;
}
}
}
#endif #endif

View File

@@ -20,6 +20,7 @@ public:
ArchitectureType GetArchitecture() const override; ArchitectureType GetArchitecture() const override;
bool UseSystemDotnet() const override; bool UseSystemDotnet() const override;
bool OnDeployBinaries(CookingData& data) override; bool OnDeployBinaries(CookingData& data) override;
void OnRun(CookingData& data, String& executableFile, String& commandLineFormat, String& workingDir) override;
}; };
#endif #endif

View File

@@ -249,4 +249,19 @@ bool MacPlatformTools::OnPostProcess(CookingData& data)
return false; return false;
} }
void MacPlatformTools::OnRun(CookingData& data, String& executableFile, String& commandLineFormat, String& workingDir)
{
// Pick the first executable file
Array<String> files;
FileSystem::DirectoryGetFiles(files, data.NativeCodeOutputPath, TEXT("*"), DirectorySearchOption::TopDirectoryOnly);
for (auto& file : files)
{
if (FileSystem::GetExtension(file).IsEmpty())
{
executableFile = file;
break;
}
}
}
#endif #endif

View File

@@ -27,6 +27,7 @@ public:
bool IsNativeCodeFile(CookingData& data, const String& file) override; bool IsNativeCodeFile(CookingData& data, const String& file) override;
void OnBuildStarted(CookingData& data) override; void OnBuildStarted(CookingData& data) override;
bool OnPostProcess(CookingData& data) override; bool OnPostProcess(CookingData& data) override;
void OnRun(CookingData& data, String& executableFile, String& commandLineFormat, String& workingDir) override;
}; };
#endif #endif

View File

@@ -225,8 +225,15 @@ namespace FlaxEditor.CustomEditors.Dedicated
} }
_actor = actor; _actor = actor;
var showActorPicker = actor == null || ParentEditor.Values.All(x => x is not Cloth); if (ParentEditor.Values.Any(x => x is Cloth))
if (showActorPicker) {
// 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 // Actor reference picker
_actorPicker = layout.Custom<FlaxObjectRefPickerControl>(); _actorPicker = layout.Custom<FlaxObjectRefPickerControl>();
@@ -242,7 +249,10 @@ namespace FlaxEditor.CustomEditors.Dedicated
{ {
var model = staticModel.Model; var model = staticModel.Model;
if (model == null || model.WaitForLoaded()) if (model == null || model.WaitForLoaded())
{
layout.Label("No model.");
return; return;
}
var materials = model.MaterialSlots; var materials = model.MaterialSlots;
var lods = model.LODs; var lods = model.LODs;
meshNames = new string[lods.Length][]; meshNames = new string[lods.Length][];
@@ -267,7 +277,10 @@ namespace FlaxEditor.CustomEditors.Dedicated
{ {
var skinnedModel = animatedModel.SkinnedModel; var skinnedModel = animatedModel.SkinnedModel;
if (skinnedModel == null || skinnedModel.WaitForLoaded()) if (skinnedModel == null || skinnedModel.WaitForLoaded())
{
layout.Label("No model.");
return; return;
}
var materials = skinnedModel.MaterialSlots; var materials = skinnedModel.MaterialSlots;
var lods = skinnedModel.LODs; var lods = skinnedModel.LODs;
meshNames = new string[lods.Length][]; meshNames = new string[lods.Length][];

View File

@@ -44,11 +44,11 @@ namespace FlaxEditor.GUI.Docking
var mousePos = window.MousePosition; var mousePos = window.MousePosition;
var previousSize = window.Size; var previousSize = window.Size;
window.Restore(); 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 // 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 // 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. // 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 // Enable hit window presentation
Proxy.Window.RenderingEnabled = true; Proxy.Window.RenderingEnabled = true;
Proxy.Window.Show(); Proxy.Window.Show();
Proxy.Window.Focus();
} }
/// <summary> /// <summary>
@@ -113,7 +114,7 @@ namespace FlaxEditor.GUI.Docking
var window = _toMove.Window?.Window; var window = _toMove.Window?.Window;
if (window == null) if (window == null)
return; return;
var mouse = FlaxEngine.Input.MouseScreenPosition; var mouse = Platform.MousePosition;
// Move base window // Move base window
window.Position = mouse - _dragOffset; window.Position = mouse - _dragOffset;
@@ -193,7 +194,7 @@ namespace FlaxEditor.GUI.Docking
// Move window to the mouse position (with some offset for caption bar) // Move window to the mouse position (with some offset for caption bar)
var window = (WindowRootControl)toMove.Root; var window = (WindowRootControl)toMove.Root;
var mouse = FlaxEngine.Input.MouseScreenPosition; var mouse = Platform.MousePosition;
window.Window.Position = mouse - new Float2(8, 8); window.Window.Position = mouse - new Float2(8, 8);
// Get floating panel // Get floating panel
@@ -244,7 +245,7 @@ namespace FlaxEditor.GUI.Docking
private void UpdateRects() private void UpdateRects()
{ {
// Cache mouse position // Cache mouse position
_mouse = FlaxEngine.Input.MouseScreenPosition; _mouse = Platform.MousePosition;
// Check intersection with any dock panel // Check intersection with any dock panel
var uiMouse = _mouse; var uiMouse = _mouse;
@@ -270,15 +271,16 @@ namespace FlaxEditor.GUI.Docking
// Cache dock rectangles // Cache dock rectangles
var size = _rectDock.Size; var size = _rectDock.Size;
var offset = _rectDock.Location; var offset = _rectDock.Location;
float BorderMargin = 4.0f; var borderMargin = 4.0f;
float ProxyHintWindowsSize2 = Proxy.HintWindowsSize * 0.5f; var hintWindowsSize = Proxy.HintWindowsSize * Platform.DpiScale;
float centerX = size.X * 0.5f; var hintWindowsSize2 = hintWindowsSize * 0.5f;
float centerY = size.Y * 0.5f; var centerX = size.X * 0.5f;
_rUpper = new Rectangle(centerX - ProxyHintWindowsSize2, BorderMargin, Proxy.HintWindowsSize, Proxy.HintWindowsSize) + offset; var centerY = size.Y * 0.5f;
_rBottom = new Rectangle(centerX - ProxyHintWindowsSize2, size.Y - Proxy.HintWindowsSize - BorderMargin, Proxy.HintWindowsSize, Proxy.HintWindowsSize) + offset; _rUpper = new Rectangle(centerX - hintWindowsSize2, borderMargin, hintWindowsSize, hintWindowsSize) + offset;
_rLeft = new Rectangle(BorderMargin, centerY - ProxyHintWindowsSize2, Proxy.HintWindowsSize, Proxy.HintWindowsSize) + offset; _rBottom = new Rectangle(centerX - hintWindowsSize2, size.Y - hintWindowsSize - borderMargin, hintWindowsSize, hintWindowsSize) + offset;
_rRight = new Rectangle(size.X - Proxy.HintWindowsSize - BorderMargin, centerY - ProxyHintWindowsSize2, Proxy.HintWindowsSize, Proxy.HintWindowsSize) + offset; _rLeft = new Rectangle(borderMargin, centerY - hintWindowsSize2, hintWindowsSize, hintWindowsSize) + offset;
_rCenter = new Rectangle(centerX - ProxyHintWindowsSize2, centerY - ProxyHintWindowsSize2, Proxy.HintWindowsSize, Proxy.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 // Hit test
DockState toSet = DockState.Float; DockState toSet = DockState.Float;
@@ -428,7 +430,6 @@ namespace FlaxEditor.GUI.Docking
{ {
if (Window == null) if (Window == null)
{ {
// Create proxy window
var settings = CreateWindowSettings.Default; var settings = CreateWindowSettings.Default;
settings.Title = "DockHint.Window"; settings.Title = "DockHint.Window";
settings.Size = initSize; settings.Size = initSize;
@@ -440,12 +441,10 @@ namespace FlaxEditor.GUI.Docking
settings.IsRegularWindow = false; settings.IsRegularWindow = false;
settings.SupportsTransparency = true; settings.SupportsTransparency = true;
settings.ShowInTaskbar = false; settings.ShowInTaskbar = false;
settings.ShowAfterFirstPaint = true; settings.ShowAfterFirstPaint = false;
settings.IsTopmost = true; settings.IsTopmost = true;
Window = Platform.CreateWindow(ref settings); Window = Platform.CreateWindow(ref settings);
// Set opacity and background color
Window.Opacity = 0.6f; Window.Opacity = 0.6f;
Window.GUI.BackgroundColor = Style.Current.DragWindow; Window.GUI.BackgroundColor = Style.Current.DragWindow;
} }
@@ -465,7 +464,7 @@ namespace FlaxEditor.GUI.Docking
var settings = CreateWindowSettings.Default; var settings = CreateWindowSettings.Default;
settings.Title = name; settings.Title = name;
settings.Size = new Float2(HintWindowsSize); settings.Size = new Float2(HintWindowsSize * Platform.DpiScale);
settings.AllowInput = false; settings.AllowInput = false;
settings.AllowMaximize = false; settings.AllowMaximize = false;
settings.AllowMinimize = false; settings.AllowMinimize = false;
@@ -479,7 +478,6 @@ namespace FlaxEditor.GUI.Docking
settings.ShowAfterFirstPaint = false; settings.ShowAfterFirstPaint = false;
win = Platform.CreateWindow(ref settings); win = Platform.CreateWindow(ref settings);
win.Opacity = 0.6f; win.Opacity = 0.6f;
win.GUI.BackgroundColor = Style.Current.DragWindow; win.GUI.BackgroundColor = Style.Current.DragWindow;
} }

View File

@@ -776,11 +776,20 @@ namespace FlaxEditor.GUI.Tree
// Check if mouse hits arrow // Check if mouse hits arrow
if (_mouseOverArrow && HasAnyVisibleChild) if (_mouseOverArrow && HasAnyVisibleChild)
{ {
// Toggle open state if (ParentTree.Root.GetKey(KeyboardKeys.Alt))
if (_opened) {
Collapse(); if (_opened)
CollapseAll();
else
ExpandAll();
}
else else
Expand(); {
if (_opened)
Collapse();
else
Expand();
}
} }
// Check if mouse hits bar // Check if mouse hits bar

View File

@@ -513,7 +513,9 @@ DEFINE_INTERNAL_CALL(void) EditorInternal_RunVisualScriptBreakpointLoopTick(floa
WindowsManager::WindowsLocker.Unlock(); WindowsManager::WindowsLocker.Unlock();
} }
WindowsManager::WindowsLocker.Lock(); WindowsManager::WindowsLocker.Lock();
for (auto& win : WindowsManager::Windows) Array<Window*, InlinedAllocation<32>> windows;
windows.Add(WindowsManager::Windows);
for (Window* win : windows)
{ {
if (win->IsVisible()) if (win->IsVisible())
win->OnUpdate(deltaTime); win->OnUpdate(deltaTime);

View File

@@ -237,7 +237,11 @@ namespace FlaxEditor.Modules
/// </summary> /// </summary>
public void LoadDefaultLayout() 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);
}
} }
/// <summary> /// <summary>

View File

@@ -259,10 +259,7 @@ namespace FlaxEditor.Options
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{ {
if (sourceType == typeof(string)) if (sourceType == typeof(string))
{
return true; return true;
}
return base.CanConvertFrom(context, sourceType); return base.CanConvertFrom(context, sourceType);
} }
@@ -270,9 +267,7 @@ namespace FlaxEditor.Options
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{ {
if (destinationType == typeof(string)) if (destinationType == typeof(string))
{
return false; return false;
}
return base.CanConvertTo(context, destinationType); return base.CanConvertTo(context, destinationType);
} }
@@ -284,7 +279,6 @@ namespace FlaxEditor.Options
InputBinding.TryParse(str, out var result); InputBinding.TryParse(str, out var result);
return result; return result;
} }
return base.ConvertFrom(context, culture, value); return base.ConvertFrom(context, culture, value);
} }
@@ -295,7 +289,6 @@ namespace FlaxEditor.Options
{ {
return ((InputBinding)value).ToString(); return ((InputBinding)value).ToString();
} }
return base.ConvertTo(context, culture, value, destinationType); return base.ConvertTo(context, culture, value, destinationType);
} }
} }

View File

@@ -26,45 +26,108 @@ namespace FlaxEditor.Options
public float MouseWheelSensitivity { get; set; } = 1.0f; public float MouseWheelSensitivity { get; set; } = 1.0f;
/// <summary> /// <summary>
/// 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.
/// </summary> /// </summary>
[DefaultValue(1.0f), Limit(0.01f, 100.0f)] [DefaultValue(64), Limit(1, 128)]
[EditorDisplay("Defaults"), EditorOrder(110), Tooltip("The default movement speed for the viewport camera (must match the dropdown menu values in the viewport).")] [EditorDisplay("Camera"), EditorOrder(110), Tooltip("The total amount of steps the camera needs to go from minimum to maximum speed.")]
public float DefaultMovementSpeed { get; set; } = 1.0f; public int TotalCameraSpeedSteps { get; set; } = 64;
/// <summary>
/// Gets or sets the degree to which the camera will be eased when using camera flight in the editor window.
/// </summary>
[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;
/// <summary>
/// Gets or sets the default movement speed for the viewport camera (must be in range between minimum and maximum movement speed values).
/// </summary>
[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;
/// <summary>
/// Gets or sets the default minimum camera movement speed.
/// </summary>
[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;
/// <summary>
/// Gets or sets the default maximum camera movement speed.
/// </summary>
[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;
/// <summary>
/// Gets or sets the default camera easing mode.
/// </summary>
[DefaultValue(true)]
[EditorDisplay("Defaults"), EditorOrder(130), Tooltip("The default camera easing mode.")]
public bool UseCameraEasing { get; set; } = true;
/// <summary> /// <summary>
/// Gets or sets the default near clipping plane distance for the viewport camera. /// Gets or sets the default near clipping plane distance for the viewport camera.
/// </summary> /// </summary>
[DefaultValue(10.0f), Limit(0.001f, 1000.0f)] [DefaultValue(10.0f), Limit(0.001f, 1000.0f)]
[EditorDisplay("Defaults"), EditorOrder(120), Tooltip("The default near clipping plane distance for the viewport camera.")] [EditorDisplay("Defaults"), EditorOrder(140), Tooltip("The default near clipping plane distance for the viewport camera.")]
public float DefaultNearPlane { get; set; } = 10.0f; public float NearPlane { get; set; } = 10.0f;
/// <summary> /// <summary>
/// Gets or sets the default far clipping plane distance for the viewport camera. /// Gets or sets the default far clipping plane distance for the viewport camera.
/// </summary> /// </summary>
[DefaultValue(40000.0f), Limit(10.0f)] [DefaultValue(40000.0f), Limit(10.0f)]
[EditorDisplay("Defaults"), EditorOrder(130), Tooltip("The default far clipping plane distance for the viewport camera.")] [EditorDisplay("Defaults"), EditorOrder(150), Tooltip("The default far clipping plane distance for the viewport camera.")]
public float DefaultFarPlane { get; set; } = 40000.0f; public float FarPlane { get; set; } = 40000.0f;
/// <summary> /// <summary>
/// Gets or sets the default field of view angle (in degrees) for the viewport camera. /// Gets or sets the default field of view angle (in degrees) for the viewport camera.
/// </summary> /// </summary>
[DefaultValue(60.0f), Limit(35.0f, 160.0f, 0.1f)] [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.")] [EditorDisplay("Defaults"), EditorOrder(160), Tooltip("The default field of view angle (in degrees) for the viewport camera.")]
public float DefaultFieldOfView { get; set; } = 60.0f; public float FieldOfView { get; set; } = 60.0f;
/// <summary> /// <summary>
/// Gets or sets if the panning direction is inverted for the viewport camera. /// Gets or sets the default camera orthographic mode.
/// </summary> /// </summary>
[DefaultValue(false)] [DefaultValue(false)]
[EditorDisplay("Defaults"), EditorOrder(150), Tooltip("Invert the panning direction for the viewport camera.")] [EditorDisplay("Defaults"), EditorOrder(170), Tooltip("The default camera orthographic mode.")]
public bool DefaultInvertPanning { get; set; } = false; public bool UseOrthographicProjection { get; set; } = false;
/// <summary> /// <summary>
/// Scales editor viewport grid. /// Gets or sets the default camera orthographic scale (if camera uses orthographic mode).
/// </summary>
[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;
/// <summary>
/// Gets or sets the default panning direction for the viewport camera.
/// </summary>
[DefaultValue(false)]
[EditorDisplay("Defaults"), EditorOrder(190), Tooltip("The default panning direction for the viewport camera.")]
public bool InvertPanning { get; set; } = false;
/// <summary>
/// Gets or sets the default relative panning mode.
/// </summary>
[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;
/// <summary>
/// Gets or sets the default panning speed (ignored if relative panning is speed enabled).
/// </summary>
[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;
/// <summary>
/// Gets or sets the default editor viewport grid scale.
/// </summary> /// </summary>
[DefaultValue(50.0f), Limit(25.0f, 500.0f, 5.0f)] [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; public float ViewportGridScale { get; set; } = 50.0f;
} }
} }

View File

@@ -154,7 +154,8 @@ bool ProjectInfo::LoadProject(const String& projectPath)
Version = ::Version( Version = ::Version(
JsonTools::GetInt(version, "Major", 0), JsonTools::GetInt(version, "Major", 0),
JsonTools::GetInt(version, "Minor", 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) if (Version.Revision() == 0)

View File

@@ -23,17 +23,11 @@ namespace FlaxEditor
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{ {
if (value == null) if (value == null)
{
writer.WriteNull(); writer.WriteNull();
}
else if (value is Version) else if (value is Version)
{
writer.WriteValue(value.ToString()); writer.WriteValue(value.ToString());
}
else else
{
throw new JsonSerializationException("Expected Version object value"); throw new JsonSerializationException("Expected Version object value");
}
} }
/// <summary> /// <summary>
@@ -47,65 +41,60 @@ namespace FlaxEditor
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{ {
if (reader.TokenType == JsonToken.Null) if (reader.TokenType == JsonToken.Null)
{
return null; return null;
}
else if (reader.TokenType == JsonToken.StartObject)
{ {
if (reader.TokenType == JsonToken.StartObject) try
{ {
try reader.Read();
var values = new Dictionary<string, int>();
while (reader.TokenType == JsonToken.PropertyName)
{ {
var key = reader.Value as string;
reader.Read(); reader.Read();
Dictionary<string, int> values = new Dictionary<string, int>(); var val = (long)reader.Value;
while (reader.TokenType == JsonToken.PropertyName) reader.Read();
{ values.Add(key, (int)val);
var key = reader.Value as string; }
reader.Read();
var val = (long)reader.Value;
reader.Read();
values.Add(key, (int)val);
}
int major = 0, minor = 0, build = 0; values.TryGetValue("Major", out var major);
values.TryGetValue("Major", out major); values.TryGetValue("Minor", out var minor);
values.TryGetValue("Minor", out minor); if (!values.TryGetValue("Build", out var build))
values.TryGetValue("Build", out build); build = -1;
if (!values.TryGetValue("Revision", out var revision))
revision = -1;
Version v = new Version(major, minor, build); if (build <= 0)
return v; return new Version(major, minor);
} if (revision <= 0)
catch (Exception ex) return new Version(major, minor, build);
{ return new Version(major, minor, build, revision);
throw new Exception(String.Format("Error parsing version string: {0}", reader.Value), ex);
}
} }
else if (reader.TokenType == JsonToken.String) catch (Exception ex)
{ {
try throw new Exception(String.Format("Error parsing version string: {0}", reader.Value), ex);
{
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));
} }
} }
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));
} }
/// <summary> /// <summary>
/// Determines whether this instance can convert the specified object type. /// Determines whether this instance can convert the specified object type.
/// </summary> /// </summary>
/// <param name="objectType">Type of the object.</param> /// <param name="objectType">Type of the object.</param>
/// <returns> /// <returns><c>true</c> if this instance can convert the specified object type; otherwise, <c>false</c>.</returns>
/// <c>true</c> if this instance can convert the specified object type; otherwise, <c>false</c>.
/// </returns>
public override bool CanConvert(Type objectType) public override bool CanConvert(Type objectType)
{ {
return objectType == typeof(Version); return objectType == typeof(Version);

View File

@@ -66,7 +66,8 @@ namespace FlaxEditor.SceneGraph.GUI
_orderInParent = actor.OrderInParent; _orderInParent = actor.OrderInParent;
Visible = (actor.HideFlags & HideFlags.HideInHierarchy) == 0; 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)) if (Editor.Instance.ProjectCache.IsExpandedActor(ref id))
{ {
Expand(true); Expand(true);
@@ -171,7 +172,8 @@ namespace FlaxEditor.SceneGraph.GUI
// Restore cached state on query filter clear // Restore cached state on query filter clear
if (noFilter && actor != null) 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); isExpanded = Editor.Instance.ProjectCache.IsExpandedActor(ref id);
} }
@@ -301,10 +303,12 @@ namespace FlaxEditor.SceneGraph.GUI
protected override void OnExpandedChanged() protected override void OnExpandedChanged()
{ {
base.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); Editor.Instance.ProjectCache.SetExpandedActor(ref id, IsExpanded);
} }
} }

View File

@@ -225,6 +225,7 @@ namespace FlaxEngine.Tools
var cloth = _cloth; var cloth = _cloth;
if (cloth == null) if (cloth == null)
return; return;
var hasPaintInput = Owner.IsLeftMouseButtonDown && !Owner.IsAltKeyDown;
// Perform detailed tracing to find cursor location for the brush // Perform detailed tracing to find cursor location for the brush
var ray = Owner.MouseRay; var ray = Owner.MouseRay;
@@ -240,7 +241,7 @@ namespace FlaxEngine.Tools
// Cursor hit other object or nothing // Cursor hit other object or nothing
PaintEnd(); PaintEnd();
if (Owner.IsLeftMouseButtonDown) if (hasPaintInput)
{ {
// Select something else // Select something else
var view = new Ray(Owner.ViewPosition, Owner.ViewDirection); var view = new Ray(Owner.ViewPosition, Owner.ViewDirection);
@@ -253,7 +254,7 @@ namespace FlaxEngine.Tools
} }
// Handle painting // Handle painting
if (Owner.IsLeftMouseButtonDown) if (hasPaintInput)
PaintStart(); PaintStart();
else else
PaintEnd(); PaintEnd();

View File

@@ -259,7 +259,10 @@ namespace FlaxEditor.Viewport.Cameras
// Pan // Pan
if (input.IsPanning) if (input.IsPanning)
{ {
var panningSpeed = 0.8f; var panningSpeed = (Viewport.RelativePanning)
? Mathf.Abs((position - TargetPoint).Length) * 0.005f
: Viewport.PanningSpeed;
if (Viewport.InvertPanning) if (Viewport.InvertPanning)
{ {
position += up * (mouseDelta.Y * panningSpeed); position += up * (mouseDelta.Y * panningSpeed);

View File

@@ -128,12 +128,26 @@ namespace FlaxEditor.Viewport
public const int FpsCameraFilteringFrames = 3; public const int FpsCameraFilteringFrames = 3;
/// <summary> /// <summary>
/// The speed widget button. /// The camera settings widget.
/// </summary> /// </summary>
protected ViewportWidgetButton _speedWidget; protected ViewportWidgetsContainer _cameraWidget;
/// <summary>
/// The camera settings widget button.
/// </summary>
protected ViewportWidgetButton _cameraButton;
/// <summary>
/// The orthographic mode widget button.
/// </summary>
protected ViewportWidgetButton _orthographicModeButton;
private readonly Editor _editor;
private float _mouseSensitivity; private float _mouseSensitivity;
private float _movementSpeed; private float _movementSpeed;
private float _minMovementSpeed;
private float _maxMovementSpeed;
private float _mouseAccelerationScale; private float _mouseAccelerationScale;
private bool _useMouseFiltering; private bool _useMouseFiltering;
private bool _useMouseAcceleration; private bool _useMouseAcceleration;
@@ -174,11 +188,17 @@ namespace FlaxEditor.Viewport
private float _fieldOfView; private float _fieldOfView;
private float _nearPlane; private float _nearPlane;
private float _farPlane; private float _farPlane;
private float _orthoSize = 1.0f; private float _orthoSize;
private bool _isOrtho = false; private bool _isOrtho;
private float _wheelMovementChangeDeltaSum = 0; private bool _useCameraEasing;
private float _cameraEasingDegree;
private float _panningSpeed;
private bool _relativePanning;
private bool _invertPanning; private bool _invertPanning;
private int _speedStep;
private int _maxSpeedSteps;
/// <summary> /// <summary>
/// Speed of the mouse. /// Speed of the mouse.
/// </summary> /// </summary>
@@ -189,6 +209,25 @@ namespace FlaxEditor.Viewport
/// </summary> /// </summary>
public float MouseWheelZoomSpeedFactor = 1; public float MouseWheelZoomSpeedFactor = 1;
/// <summary>
/// Format of the text for the camera move speed.
/// </summary>
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:#}";
}
}
/// <summary> /// <summary>
/// Gets or sets the camera movement speed. /// Gets or sets the camera movement speed.
/// </summary> /// </summary>
@@ -197,19 +236,40 @@ namespace FlaxEditor.Viewport
get => _movementSpeed; get => _movementSpeed;
set set
{ {
for (int i = 0; i < EditorViewportCameraSpeedValues.Length; i++) _movementSpeed = value;
{
if (Math.Abs(value - EditorViewportCameraSpeedValues[i]) < 0.001f) if (_cameraButton != null)
{ _cameraButton.Text = string.Format(MovementSpeedTextFormat, _movementSpeed);
_movementSpeed = EditorViewportCameraSpeedValues[i];
if (_speedWidget != null)
_speedWidget.Text = _movementSpeed.ToString();
break;
}
}
} }
} }
/// <summary>
/// Gets or sets the minimum camera movement speed.
/// </summary>
public float MinMovementSpeed
{
get => _minMovementSpeed;
set => _minMovementSpeed = value;
}
/// <summary>
/// Gets or sets the maximum camera movement speed.
/// </summary>
public float MaxMovementSpeed
{
get => _maxMovementSpeed;
set => _maxMovementSpeed = value;
}
/// <summary>
/// Gets or sets the camera easing mode.
/// </summary>
public bool UseCameraEasing
{
get => _useCameraEasing;
set => _useCameraEasing = value;
}
/// <summary> /// <summary>
/// Gets the mouse movement position delta (user press and move). /// Gets the mouse movement position delta (user press and move).
/// </summary> /// </summary>
@@ -396,6 +456,15 @@ namespace FlaxEditor.Viewport
set => _isOrtho = value; set => _isOrtho = value;
} }
/// <summary>
/// Gets or sets if the panning speed should be relative to the camera target.
/// </summary>
public bool RelativePanning
{
get => _relativePanning;
set => _relativePanning = value;
}
/// <summary> /// <summary>
/// Gets or sets if the panning direction is inverted. /// Gets or sets if the panning direction is inverted.
/// </summary> /// </summary>
@@ -405,6 +474,15 @@ namespace FlaxEditor.Viewport
set => _invertPanning = value; set => _invertPanning = value;
} }
/// <summary>
/// Gets or sets the camera panning speed.
/// </summary>
public float PanningSpeed
{
get => _panningSpeed;
set => _panningSpeed = value;
}
/// <summary> /// <summary>
/// The input actions collection to processed during user input. /// The input actions collection to processed during user input.
/// </summary> /// </summary>
@@ -419,6 +497,8 @@ namespace FlaxEditor.Viewport
public EditorViewport(SceneRenderTask task, ViewportCamera camera, bool useWidgets) public EditorViewport(SceneRenderTask task, ViewportCamera camera, bool useWidgets)
: base(task) : base(task)
{ {
_editor = Editor.Instance;
_mouseAccelerationScale = 0.1f; _mouseAccelerationScale = 0.1f;
_useMouseFiltering = false; _useMouseFiltering = false;
_useMouseAcceleration = false; _useMouseAcceleration = false;
@@ -431,43 +511,299 @@ namespace FlaxEditor.Viewport
// Setup options // 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; 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) if (useWidgets)
{ {
var largestText = "Invert Panning"; #region Camera settings widget
var largestText = "Relative Panning";
var textSize = Style.Current.FontMedium.MeasureText(largestText); var textSize = Style.Current.FontMedium.MeasureText(largestText);
var xLocationForExtras = textSize.X + 5; var xLocationForExtras = textSize.X + 5;
// Camera speed widget var cameraSpeedTextWidth = Style.Current.FontMedium.MeasureText("0.00").X;
var camSpeed = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
var camSpeedCM = new ContextMenu(); // Camera Settings Widget
var camSpeedButton = new ViewportWidgetButton(_movementSpeed.ToString(), Editor.Instance.Icons.CamSpeed32, camSpeedCM) _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, Tag = this,
TooltipText = "Camera speed scale" TooltipText = "Camera Settings",
Parent = _cameraWidget
}; };
_speedWidget = camSpeedButton; _cameraWidget.Parent = this;
for (int i = 0; i < EditorViewportCameraSpeedValues.Length; i++)
{ // Orthographic/Perspective Mode Widget
var v = EditorViewportCameraSpeedValues[i]; _orthographicModeButton = new ViewportWidgetButton(string.Empty, Editor.Instance.Icons.CamSpeed32, null, true)
var button = camSpeedCM.AddButton(v.ToString()); {
button.Tag = v; Checked = !_isOrtho,
} TooltipText = "Toggle Orthographic/Perspective Mode",
camSpeedCM.ButtonClicked += button => MovementSpeed = (float)button.Tag; Parent = _cameraWidget
camSpeedCM.VisibleChanged += WidgetCamSpeedShowHide; };
camSpeedButton.Parent = camSpeed; _orthographicModeButton.Toggled += OnOrthographicModeToggled;
camSpeed.Parent = this;
// 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); var viewMode = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperLeft);
ViewWidgetButtonMenu = new ContextMenu(); ViewWidgetButtonMenu = new ContextMenu();
var viewModeButton = new ViewportWidgetButton("View", SpriteHandle.Invalid, ViewWidgetButtonMenu) var viewModeButton = new ViewportWidgetButton("View", SpriteHandle.Invalid, ViewWidgetButtonMenu)
@@ -484,8 +820,8 @@ namespace FlaxEditor.Viewport
// Show FPS // Show FPS
{ {
InitFpsCounter(); InitFpsCounter();
_showFpsButon = ViewWidgetShowMenu.AddButton("FPS Counter", () => ShowFpsCounter = !ShowFpsCounter); _showFpsButton = ViewWidgetShowMenu.AddButton("FPS Counter", () => ShowFpsCounter = !ShowFpsCounter);
_showFpsButon.CloseMenuOnClick = false; _showFpsButton.CloseMenuOnClick = false;
} }
} }
@@ -610,104 +946,6 @@ namespace FlaxEditor.Viewport
ViewWidgetButtonMenu.AddSeparator(); 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 // Brightness
{ {
var brightness = ViewWidgetButtonMenu.AddButton("Brightness"); var brightness = ViewWidgetButtonMenu.AddButton("Brightness");
@@ -732,24 +970,7 @@ namespace FlaxEditor.Viewport
ViewWidgetButtonMenu.VisibleChanged += control => resolutionValue.Value = ResolutionScale; ViewWidgetButtonMenu.VisibleChanged += control => resolutionValue.Value = ResolutionScale;
} }
// Invert Panning #endregion View mode widget
{
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;
}
} }
InputActions.Add(options => options.ViewpointTop, () => OrientViewport(Quaternion.Euler(EditorViewportCameraViewpointValues.First(vp => vp.Name == "Top").Orientation))); 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; task.Begin += OnRenderBegin;
} }
/// <summary>
/// Sets the viewport options to the default values.
/// </summary>
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());
}
/// <summary> /// <summary>
/// Gets a value indicating whether this viewport is using mouse currently (eg. user moving objects). /// Gets a value indicating whether this viewport is using mouse currently (eg. user moving objects).
/// </summary> /// </summary>
@@ -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);
}
/// <summary> /// <summary>
/// Increases or decreases the camera movement speed. /// Increases or decreases the camera movement speed.
/// </summary> /// </summary>
/// <param name="step">The stepping direction for speed adjustment.</param> /// <param name="step">The stepping direction for speed adjustment.</param>
protected void AdjustCameraMoveSpeed(int step) protected void AdjustCameraMoveSpeed(int step)
{ {
int camValueIndex = -1; _speedStep = Mathf.Clamp(_speedStep + step, 0, _maxSpeedSteps);
for (int i = 0; i < EditorViewportCameraSpeedValues.Length; i++)
{
if (Mathf.NearEqual(EditorViewportCameraSpeedValues[i], _movementSpeed))
{
camValueIndex = i;
break;
}
}
if (camValueIndex == -1)
return;
if (step > 0) // calculate new linear/eased progress
MovementSpeed = EditorViewportCameraSpeedValues[Mathf.Min(camValueIndex + 1, EditorViewportCameraSpeedValues.Length - 1)]; var progress = _useCameraEasing
else if (step < 0) ? Mathf.Pow((float)_speedStep / _maxSpeedSteps, _cameraEasingDegree)
MovementSpeed = EditorViewportCameraSpeedValues[Mathf.Max(camValueIndex - 1, 0)]; : (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) private void OnEditorOptionsChanged(EditorOptions options)
{ {
_mouseSensitivity = options.Viewport.MouseSensitivity; _mouseSensitivity = options.Viewport.MouseSensitivity;
_maxSpeedSteps = options.Viewport.TotalCameraSpeedSteps;
_cameraEasingDegree = options.Viewport.CameraEasingDegree;
OnCameraMovementProgressChanged();
} }
private void OnRenderBegin(RenderTask task, GPUContext context) private void OnRenderBegin(RenderTask task, GPUContext context)
@@ -863,7 +1239,7 @@ namespace FlaxEditor.Viewport
} }
private FpsCounter _fpsCounter; private FpsCounter _fpsCounter;
private ContextMenuButton _showFpsButon; private ContextMenuButton _showFpsButton;
/// <summary> /// <summary>
/// Gets or sets a value indicating whether show or hide FPS counter. /// Gets or sets a value indicating whether show or hide FPS counter.
@@ -875,7 +1251,7 @@ namespace FlaxEditor.Viewport
{ {
_fpsCounter.Visible = value; _fpsCounter.Visible = value;
_fpsCounter.Enabled = 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
/// <param name="win">The parent window.</param> /// <param name="win">The parent window.</param>
protected virtual void OnControlMouseBegin(Window win) protected virtual void OnControlMouseBegin(Window win)
{ {
_wheelMovementChangeDeltaSum = 0;
// Hide cursor and start tracking mouse movement // Hide cursor and start tracking mouse movement
win.StartTrackingMouse(false); win.StartTrackingMouse(false);
win.Cursor = CursorType.Hidden; win.Cursor = CursorType.Hidden;
@@ -1113,8 +1487,8 @@ namespace FlaxEditor.Viewport
_camera.Update(deltaTime); _camera.Update(deltaTime);
useMovementSpeed = _camera.UseMovementSpeed; useMovementSpeed = _camera.UseMovementSpeed;
if (_speedWidget != null) if (_cameraButton != null)
_speedWidget.Parent.Visible = useMovementSpeed; _cameraButton.Parent.Visible = useMovementSpeed;
} }
// Get parent window // Get parent window
@@ -1217,18 +1591,8 @@ namespace FlaxEditor.Viewport
rmbWheel = useMovementSpeed && (_input.IsMouseRightDown || _isVirtualMouseRightDown) && wheelInUse; rmbWheel = useMovementSpeed && (_input.IsMouseRightDown || _isVirtualMouseRightDown) && wheelInUse;
if (rmbWheel) if (rmbWheel)
{ {
const float step = 4.0f; var step = _input.MouseWheelDelta * options.Viewport.MouseWheelSensitivity;
_wheelMovementChangeDeltaSum += _input.MouseWheelDelta * options.Viewport.MouseWheelSensitivity; AdjustCameraMoveSpeed(step > 0.0f ? 1 : -1);
if (_wheelMovementChangeDeltaSum >= step)
{
_wheelMovementChangeDeltaSum -= step;
AdjustCameraMoveSpeed(1);
}
else if (_wheelMovementChangeDeltaSum <= -step)
{
_wheelMovementChangeDeltaSum += step;
AdjustCameraMoveSpeed(-1);
}
} }
} }
@@ -1497,22 +1861,6 @@ namespace FlaxEditor.Viewport
new CameraViewpoint("Bottom", new Float3(-90, 0, 0)) 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 private struct ViewModeOptions
{ {
public readonly string Name; public readonly string Name;
@@ -1568,24 +1916,6 @@ namespace FlaxEditor.Viewport
new ViewModeOptions(ViewMode.GlobalIllumination, "Global Illumination"), 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) private void WidgetViewModeShowHideClicked(ContextMenuButton button)
{ {
if (button.Tag is ViewMode v) if (button.Tag is ViewMode v)

View File

@@ -19,6 +19,7 @@ namespace FlaxEditor.Viewport.Widgets
private bool _checked; private bool _checked;
private bool _autoCheck; private bool _autoCheck;
private bool _isMosueDown; private bool _isMosueDown;
private float _forcedTextWidth;
/// <summary> /// <summary>
/// Event fired when user toggles checked state. /// Event fired when user toggles checked state.
@@ -63,14 +64,16 @@ namespace FlaxEditor.Viewport.Widgets
/// <param name="text">The text.</param> /// <param name="text">The text.</param>
/// <param name="icon">The icon.</param> /// <param name="icon">The icon.</param>
/// <param name="contextMenu">The context menu.</param> /// <param name="contextMenu">The context menu.</param>
/// <param name="autoCheck">if set to <c>true</c> will be automatic checked on mouse click.</param> /// <param name="autoCheck">If set to <c>true</c> will be automatic checked on mouse click.</param>
public ViewportWidgetButton(string text, SpriteHandle icon, ContextMenu contextMenu = null, bool autoCheck = false) /// <param name="textWidth">Forces the text to be drawn with the specified width.</param>
: base(0, 0, CalculateButtonWidth(0, icon.IsValid), ViewportWidgetsContainer.WidgetsHeight) 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; _text = text;
Icon = icon; Icon = icon;
_cm = contextMenu; _cm = contextMenu;
_autoCheck = autoCheck; _autoCheck = autoCheck;
_forcedTextWidth = textWidth;
if (_cm != null) if (_cm != null)
_cm.VisibleChanged += CmOnVisibleChanged; _cm.VisibleChanged += CmOnVisibleChanged;
@@ -160,7 +163,7 @@ namespace FlaxEditor.Viewport.Widgets
var style = Style.Current; var style = Style.Current;
if (style != null && style.FontMedium) 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);
} }
} }
} }

View File

@@ -149,6 +149,7 @@ namespace FlaxEditor.Windows.Assets
// Prefab structure tree // Prefab structure tree
Graph = new LocalSceneGraph(new CustomRootNode(this)); Graph = new LocalSceneGraph(new CustomRootNode(this));
Graph.Root.TreeNode.Expand(true);
_tree = new PrefabTree _tree = new PrefabTree
{ {
Margin = new Margin(0.0f, 0.0f, -16.0f, _treePanel.ScrollBarsSize), // Hide root node 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; Graph.MainActor = _viewport.Instance;
Selection.Clear(); Selection.Clear();
Select(Graph.Main); Select(Graph.Main);
Graph.Root.TreeNode.ExpandAll(true); Graph.Root.TreeNode.Expand(true);
_undo.Clear(); _undo.Clear();
ClearEditedFlag(); ClearEditedFlag();
} }
@@ -413,7 +414,7 @@ namespace FlaxEditor.Windows.Assets
_focusCamera = true; _focusCamera = true;
Selection.Clear(); Selection.Clear();
Select(Graph.Main); Select(Graph.Main);
Graph.Root.TreeNode.ExpandAll(true); Graph.Root.TreeNode.Expand(true);
_undo.Clear(); _undo.Clear();
ClearEditedFlag(); ClearEditedFlag();

View File

@@ -369,7 +369,7 @@ namespace FlaxEditor.Windows
} }
var pluginPath = Path.Combine(Globals.ProjectFolder, "Source", nameTextBox.Text); 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.BorderColor = Color.Red;
nameTextBox.BorderSelectedColor = Color.Red; nameTextBox.BorderSelectedColor = Color.Red;
@@ -429,6 +429,12 @@ namespace FlaxEditor.Windows
submitButton.Clicked += () => submitButton.Clicked += () =>
{ {
// TODO: Check all modules in project including plugins // 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))) if (Directory.Exists(Path.Combine(Globals.ProjectFolder, "Source", nameTextBox.Text)))
{ {
Editor.LogWarning("Cannot create module due to name conflict."); Editor.LogWarning("Cannot create module due to name conflict.");
@@ -460,5 +466,16 @@ namespace FlaxEditor.Windows
button.ParentContextMenu.Hide(); 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;
}
} }
} }

View File

@@ -155,29 +155,42 @@ namespace FlaxEditor.Windows
public virtual void OnNotAvailableLayout(LayoutElementsContainer layout) 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()) if (FlaxEditor.Editor.IsOfficialBuild())
{ {
switch (BuildPlatform) switch (BuildPlatform)
{ {
#if PLATFORM_WINDOWS
case BuildPlatform.Windows32: case BuildPlatform.Windows32:
case BuildPlatform.Windows64: case BuildPlatform.Windows64:
case BuildPlatform.UWPx86: case BuildPlatform.UWPx86:
case BuildPlatform.UWPx64: case BuildPlatform.UWPx64:
case BuildPlatform.LinuxX64: case BuildPlatform.LinuxX64:
case BuildPlatform.AndroidARM64: 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; break;
#endif
default: default:
layout.Label("Engine source is required to target this platform.", TextAlignment.Center); text += "\nEngine source is required to target this platform.";
break; break;
} }
} }
else 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); text += "\nTo target this platform separate engine source package is required.";
label.Label.AutoHeight = true; 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() public virtual void Build()

View File

@@ -82,7 +82,6 @@ public:
private: private:
int32 _elementsCount = 0; int32 _elementsCount = 0;
int32 _deletedCount = 0;
int32 _size = 0; int32 _size = 0;
AllocationData _allocation; AllocationData _allocation;
@@ -109,14 +108,11 @@ public:
/// <param name="other">The other collection to move.</param> /// <param name="other">The other collection to move.</param>
HashSet(HashSet&& other) noexcept HashSet(HashSet&& other) noexcept
: _elementsCount(other._elementsCount) : _elementsCount(other._elementsCount)
, _deletedCount(other._deletedCount)
, _size(other._size) , _size(other._size)
{ {
_elementsCount = other._elementsCount; _elementsCount = other._elementsCount;
_deletedCount = other._deletedCount;
_size = other._size; _size = other._size;
other._elementsCount = 0; other._elementsCount = 0;
other._deletedCount = 0;
other._size = 0; other._size = 0;
_allocation.Swap(other._allocation); _allocation.Swap(other._allocation);
} }
@@ -154,10 +150,8 @@ public:
Clear(); Clear();
_allocation.Free(); _allocation.Free();
_elementsCount = other._elementsCount; _elementsCount = other._elementsCount;
_deletedCount = other._deletedCount;
_size = other._size; _size = other._size;
other._elementsCount = 0; other._elementsCount = 0;
other._deletedCount = 0;
other._size = 0; other._size = 0;
_allocation.Swap(other._allocation); _allocation.Swap(other._allocation);
} }
@@ -337,12 +331,12 @@ public:
/// </summary> /// </summary>
void Clear() void Clear()
{ {
if (_elementsCount + _deletedCount != 0) if (_elementsCount != 0)
{ {
Bucket* data = _allocation.Get(); Bucket* data = _allocation.Get();
for (int32 i = 0; i < _size; i++) for (int32 i = 0; i < _size; i++)
data[i].Free(); data[i].Free();
_elementsCount = _deletedCount = 0; _elementsCount = 0;
} }
} }
@@ -377,7 +371,7 @@ public:
oldAllocation.Swap(_allocation); oldAllocation.Swap(_allocation);
const int32 oldSize = _size; const int32 oldSize = _size;
const int32 oldElementsCount = _elementsCount; const int32 oldElementsCount = _elementsCount;
_deletedCount = _elementsCount = 0; _elementsCount = 0;
if (capacity != 0 && (capacity & (capacity - 1)) != 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) // 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) bool Add(const ItemType& item)
{ {
// Ensure to have enough memory for the next item (in case of new element insertion) // 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 // Find location of the item or place to insert it
FindPositionResult pos; FindPositionResult pos;
@@ -485,7 +479,6 @@ public:
{ {
_allocation.Get()[pos.ObjectIndex].Delete(); _allocation.Get()[pos.ObjectIndex].Delete();
_elementsCount--; _elementsCount--;
_deletedCount++;
return true; return true;
} }
return false; return false;
@@ -504,7 +497,6 @@ public:
ASSERT(_allocation.Get()[i._index].IsOccupied()); ASSERT(_allocation.Get()[i._index].IsOccupied());
_allocation.Get()[i._index].Delete(); _allocation.Get()[i._index].Delete();
_elementsCount--; _elementsCount--;
_deletedCount++;
return true; return true;
} }
return false; return false;

View File

@@ -511,7 +511,7 @@ public:
_locker = New<CriticalSection>(); _locker = New<CriticalSection>();
ScopeLock lock(*_locker); ScopeLock lock(*_locker);
if (_functions == nullptr) if (_functions == nullptr)
_functions = New<HashSet<FunctionType>>(32); _functions = New<HashSet<FunctionType>>();
_functions->Add(f); _functions->Add(f);
#endif #endif
} }
@@ -583,18 +583,9 @@ public:
template<void(*Method)(Params...)> template<void(*Method)(Params...)>
void Unbind() void Unbind()
{ {
#if DELEGATE_USE_ATOMIC
FunctionType f; FunctionType f;
f.template Bind<Method>(); f.template Bind<Method>();
Unbind(f); Unbind(f);
#else
if (_functions == nullptr)
return;
FunctionType f;
f.template Bind<Method>();
ScopeLock lock(*_locker);
_functions->Remove(f);
#endif
} }
/// <summary> /// <summary>
@@ -604,18 +595,9 @@ public:
template<class T, void(T::*Method)(Params...)> template<class T, void(T::*Method)(Params...)>
void Unbind(T* callee) void Unbind(T* callee)
{ {
#if DELEGATE_USE_ATOMIC
FunctionType f; FunctionType f;
f.template Bind<T, Method>(callee); f.template Bind<T, Method>(callee);
Unbind(f); Unbind(f);
#else
if (_functions == nullptr)
return;
FunctionType f;
f.template Bind<T, Method>(callee);
ScopeLock lock(*_locker);
_functions->Remove(f);
#endif
} }
/// <summary> /// <summary>
@@ -624,16 +606,8 @@ public:
/// <param name="method">The method.</param> /// <param name="method">The method.</param>
void Unbind(Signature method) void Unbind(Signature method)
{ {
#if DELEGATE_USE_ATOMIC
FunctionType f(method); FunctionType f(method);
Unbind(f); Unbind(f);
#else
if (_functions == nullptr)
return;
FunctionType f(method);
ScopeLock lock(*_locker);
_functions->Remove(f);
#endif
} }
/// <summary> /// <summary>

View File

@@ -13,9 +13,7 @@ namespace FlaxEngine.TypeConverters
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{ {
if (sourceType == typeof(string)) if (sourceType == typeof(string))
{
return true; return true;
}
return base.CanConvertFrom(context, sourceType); return base.CanConvertFrom(context, sourceType);
} }
@@ -23,9 +21,7 @@ namespace FlaxEngine.TypeConverters
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{ {
if (destinationType == typeof(string)) if (destinationType == typeof(string))
{
return false; return false;
}
return base.CanConvertTo(context, destinationType); return base.CanConvertTo(context, destinationType);
} }

View File

@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters namespace FlaxEngine.TypeConverters
{ {
internal class Double2Converter : TypeConverter internal class Double2Converter : VectorConverter
{ {
/// <inheritdoc />
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
/// <inheritdoc />
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
{
return false;
}
return base.CanConvertTo(context, destinationType);
}
/// <inheritdoc /> /// <inheritdoc />
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{ {
if (value is string str) 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 new Double2(double.Parse(v[0], culture), double.Parse(v[1], culture));
} }
return base.ConvertFrom(context, culture, value); return base.ConvertFrom(context, culture, value);

View File

@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters namespace FlaxEngine.TypeConverters
{ {
internal class Double3Converter : TypeConverter internal class Double3Converter : VectorConverter
{ {
/// <inheritdoc />
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
/// <inheritdoc />
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
{
return false;
}
return base.CanConvertTo(context, destinationType);
}
/// <inheritdoc /> /// <inheritdoc />
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{ {
if (value is string str) 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 new Double3(double.Parse(v[0], culture), double.Parse(v[1], culture), double.Parse(v[2], culture));
} }
return base.ConvertFrom(context, culture, value); return base.ConvertFrom(context, culture, value);

View File

@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters namespace FlaxEngine.TypeConverters
{ {
internal class Double4Converter : TypeConverter internal class Double4Converter : VectorConverter
{ {
/// <inheritdoc />
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
/// <inheritdoc />
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
{
return false;
}
return base.CanConvertTo(context, destinationType);
}
/// <inheritdoc /> /// <inheritdoc />
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{ {
if (value is string str) 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 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); return base.ConvertFrom(context, culture, value);

View File

@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters namespace FlaxEngine.TypeConverters
{ {
internal class Float2Converter : TypeConverter internal class Float2Converter : VectorConverter
{ {
/// <inheritdoc />
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
/// <inheritdoc />
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
{
return false;
}
return base.CanConvertTo(context, destinationType);
}
/// <inheritdoc /> /// <inheritdoc />
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{ {
if (value is string str) 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 new Float2(float.Parse(v[0], culture), float.Parse(v[1], culture));
} }
return base.ConvertFrom(context, culture, value); return base.ConvertFrom(context, culture, value);

View File

@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters namespace FlaxEngine.TypeConverters
{ {
internal class Float3Converter : TypeConverter internal class Float3Converter : VectorConverter
{ {
/// <inheritdoc />
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
/// <inheritdoc />
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
{
return false;
}
return base.CanConvertTo(context, destinationType);
}
/// <inheritdoc /> /// <inheritdoc />
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{ {
if (value is string str) 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 new Float3(float.Parse(v[0], culture), float.Parse(v[1], culture), float.Parse(v[2], culture));
} }
return base.ConvertFrom(context, culture, value); return base.ConvertFrom(context, culture, value);

View File

@@ -7,15 +7,13 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters namespace FlaxEngine.TypeConverters
{ {
internal class Float4Converter : TypeConverter internal class VectorConverter : TypeConverter
{ {
/// <inheritdoc /> /// <inheritdoc />
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{ {
if (sourceType == typeof(string)) if (sourceType == typeof(string))
{
return true; return true;
}
return base.CanConvertFrom(context, sourceType); return base.CanConvertFrom(context, sourceType);
} }
@@ -23,18 +21,32 @@ namespace FlaxEngine.TypeConverters
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{ {
if (destinationType == typeof(string)) if (destinationType == typeof(string))
{
return false; return false;
}
return base.CanConvertTo(context, destinationType); 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
{
/// <inheritdoc /> /// <inheritdoc />
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{ {
if (value is string str) 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 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); return base.ConvertFrom(context, culture, value);

View File

@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters namespace FlaxEngine.TypeConverters
{ {
internal class Int2Converter : TypeConverter internal class Int2Converter : VectorConverter
{ {
/// <inheritdoc />
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
/// <inheritdoc />
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
{
return false;
}
return base.CanConvertTo(context, destinationType);
}
/// <inheritdoc /> /// <inheritdoc />
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{ {
if (value is string str) 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 new Int2(int.Parse(v[0], culture), int.Parse(v[1], culture));
} }
return base.ConvertFrom(context, culture, value); return base.ConvertFrom(context, culture, value);

View File

@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters namespace FlaxEngine.TypeConverters
{ {
internal class Int3Converter : TypeConverter internal class Int3Converter : VectorConverter
{ {
/// <inheritdoc />
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
/// <inheritdoc />
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
{
return false;
}
return base.CanConvertTo(context, destinationType);
}
/// <inheritdoc /> /// <inheritdoc />
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{ {
if (value is string str) 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 new Int3(int.Parse(v[0], culture), int.Parse(v[1], culture), int.Parse(v[2], culture));
} }
return base.ConvertFrom(context, culture, value); return base.ConvertFrom(context, culture, value);

View File

@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters namespace FlaxEngine.TypeConverters
{ {
internal class Int4Converter : TypeConverter internal class Int4Converter : VectorConverter
{ {
/// <inheritdoc />
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
/// <inheritdoc />
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
{
return false;
}
return base.CanConvertTo(context, destinationType);
}
/// <inheritdoc /> /// <inheritdoc />
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{ {
if (value is string str) 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 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); return base.ConvertFrom(context, culture, value);

View File

@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters namespace FlaxEngine.TypeConverters
{ {
internal class QuaternionConverter : TypeConverter internal class QuaternionConverter : VectorConverter
{ {
/// <inheritdoc />
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
/// <inheritdoc />
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
{
return false;
}
return base.CanConvertTo(context, destinationType);
}
/// <inheritdoc /> /// <inheritdoc />
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{ {
if (value is string str) 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 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); return base.ConvertFrom(context, culture, value);

View File

@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters namespace FlaxEngine.TypeConverters
{ {
internal class Vector2Converter : TypeConverter internal class Vector2Converter : VectorConverter
{ {
/// <inheritdoc />
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
/// <inheritdoc />
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
{
return false;
}
return base.CanConvertTo(context, destinationType);
}
/// <inheritdoc /> /// <inheritdoc />
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{ {
if (value is string str) 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 new Vector2(float.Parse(v[0], culture), float.Parse(v[1], culture));
} }
return base.ConvertFrom(context, culture, value); return base.ConvertFrom(context, culture, value);

View File

@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters namespace FlaxEngine.TypeConverters
{ {
internal class Vector3Converter : TypeConverter internal class Vector3Converter : VectorConverter
{ {
/// <inheritdoc />
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
/// <inheritdoc />
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
{
return false;
}
return base.CanConvertTo(context, destinationType);
}
/// <inheritdoc /> /// <inheritdoc />
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{ {
if (value is string str) 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 new Vector3(float.Parse(v[0], culture), float.Parse(v[1], culture), float.Parse(v[2], culture));
} }
return base.ConvertFrom(context, culture, value); return base.ConvertFrom(context, culture, value);

View File

@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters namespace FlaxEngine.TypeConverters
{ {
internal class Vector4Converter : TypeConverter internal class Vector4Converter : VectorConverter
{ {
/// <inheritdoc />
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
/// <inheritdoc />
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
{
return false;
}
return base.CanConvertTo(context, destinationType);
}
/// <inheritdoc /> /// <inheritdoc />
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{ {
if (value is string str) 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 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); return base.ConvertFrom(context, culture, value);

View File

@@ -646,6 +646,12 @@ inline Vector2Base<T> operator/(typename TOtherFloat<T>::Type a, const Vector2Ba
return Vector2Base<T>(a) / b; return Vector2Base<T>(a) / b;
} }
template<typename T>
inline uint32 GetHash(const Vector2Base<T>& key)
{
return (*(uint32*)&key.X * 397) ^ *(uint32*)&key.Y;
}
namespace Math namespace Math
{ {
template<typename T> template<typename T>

View File

@@ -977,6 +977,12 @@ inline Vector3Base<T> operator/(typename TOtherFloat<T>::Type a, const Vector3Ba
return Vector3Base<T>(a) / b; return Vector3Base<T>(a) / b;
} }
template<typename T>
inline uint32 GetHash(const Vector3Base<T>& key)
{
return (((*(uint32*)&key.X * 397) ^ *(uint32*)&key.Y) * 397) ^ *(uint32*)&key.Z;
}
namespace Math namespace Math
{ {
template<typename T> template<typename T>

View File

@@ -552,6 +552,12 @@ inline Vector4Base<T> operator/(typename TOtherFloat<T>::Type a, const Vector4Ba
return Vector4Base<T>(a) / b; return Vector4Base<T>(a) / b;
} }
template<typename T>
inline uint32 GetHash(const Vector4Base<T>& key)
{
return (((((*(uint32*)&key.X * 397) ^ *(uint32*)&key.Y) * 397) ^ *(uint32*)&key.Z) * 397) ^*(uint32*)&key.W;
}
namespace Math namespace Math
{ {
template<typename T> template<typename T>

View File

@@ -5,6 +5,7 @@
#include "Collections/Dictionary.h" #include "Collections/Dictionary.h"
#include "Engine/Engine/Time.h" #include "Engine/Engine/Time.h"
#include "Engine/Engine/EngineService.h" #include "Engine/Engine/EngineService.h"
#include "Engine/Platform/CriticalSection.h"
#include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Scripting/ScriptingObject.h" #include "Engine/Scripting/ScriptingObject.h"

View File

@@ -7,15 +7,15 @@ Version::Version(int32 major, int32 minor, int32 build, int32 revision)
{ {
_major = Math::Max(major, 0); _major = Math::Max(major, 0);
_minor = Math::Max(minor, 0); _minor = Math::Max(minor, 0);
_build = Math::Max(build, 0); _build = Math::Max(build, -1);
_revision = Math::Max(revision, 0); _revision = Math::Max(revision, -1);
} }
Version::Version(int32 major, int32 minor, int32 build) Version::Version(int32 major, int32 minor, int32 build)
{ {
_major = Math::Max(major, 0); _major = Math::Max(major, 0);
_minor = Math::Max(minor, 0); _minor = Math::Max(minor, 0);
_build = Math::Max(build, 0); _build = Math::Max(build, -1);
_revision = -1; _revision = -1;
} }

View File

@@ -51,10 +51,14 @@ namespace Utilities
int32 i = 0; int32 i = 0;
double dblSUnits = static_cast<double>(units); double dblSUnits = static_cast<double>(units);
for (; static_cast<uint64>(units / static_cast<double>(divider)) > 0; i++, units /= divider) for (; static_cast<uint64>(units / static_cast<double>(divider)) > 0; i++, units /= divider)
dblSUnits = units / static_cast<double>(divider); dblSUnits = (double)units / (double)divider;
if (i >= sizes.Length()) if (i >= sizes.Length())
i = 0; 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 // Converts size of the file (in bytes) to the best fitting string

View File

@@ -1302,7 +1302,8 @@ namespace FlaxEngine.Interop
#if !USE_AOT #if !USE_AOT
internal bool TryGetDelegate(out Invoker.MarshalAndInvokeDelegate outDeleg, out object outDelegInvoke) 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<Type> methodTypes = new List<Type>(); List<Type> methodTypes = new List<Type>();
if (!method.IsStatic) if (!method.IsStatic)

View File

@@ -313,6 +313,8 @@ bool GPUDevice::Init()
_res->TasksManager.SetExecutor(CreateTasksExecutor()); _res->TasksManager.SetExecutor(CreateTasksExecutor());
LOG(Info, "Total graphics memory: {0}", Utilities::BytesToText(TotalGraphicsMemory)); LOG(Info, "Total graphics memory: {0}", Utilities::BytesToText(TotalGraphicsMemory));
if (!Limits.HasCompute)
LOG(Warning, "Compute Shaders are not supported");
return false; return false;
} }

View File

@@ -87,7 +87,11 @@ bool GPUShader::Create(MemoryReadStream& stream)
GPUShaderProgramInitializer initializer; GPUShaderProgramInitializer initializer;
#if !BUILD_RELEASE #if !BUILD_RELEASE
initializer.Owner = this; initializer.Owner = this;
const StringView name = GetName();
#else
const StringView name;
#endif #endif
const bool hasCompute = GPUDevice::Instance->Limits.HasCompute;
for (int32 i = 0; i < shadersCount; i++) for (int32 i = 0; i < shadersCount; i++)
{ {
const ShaderStage type = static_cast<ShaderStage>(stream.ReadByte()); const ShaderStage type = static_cast<ShaderStage>(stream.ReadByte());
@@ -117,10 +121,15 @@ bool GPUShader::Create(MemoryReadStream& stream)
stream.ReadBytes(&initializer.Bindings, sizeof(ShaderBindings)); stream.ReadBytes(&initializer.Bindings, sizeof(ShaderBindings));
// Create shader program // 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); GPUShaderProgram* shader = CreateGPUShaderProgram(type, initializer, cache, cacheSize, stream);
if (shader == nullptr) 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; return true;
} }

View File

@@ -122,6 +122,19 @@ public:
/// </summary> /// </summary>
class GPUShaderProgramVS : public GPUShaderProgram 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: public:
/// <summary> /// <summary>
/// Gets input layout description handle (platform dependent). /// Gets input layout description handle (platform dependent).

View File

@@ -15,32 +15,21 @@ GPUShaderProgram* GPUShaderDX11::CreateGPUShaderProgram(ShaderStage type, const
{ {
case ShaderStage::Vertex: case ShaderStage::Vertex:
{ {
D3D11_INPUT_ELEMENT_DESC inputLayoutDesc[VERTEX_SHADER_MAX_INPUT_ELEMENTS]; // Load Input Layout
// Temporary variables
byte Type, Format, Index, InputSlot, InputSlotClass;
uint32 AlignedByteOffset, InstanceDataStepRate;
// Load Input Layout (it may be empty)
byte inputLayoutSize; byte inputLayoutSize;
stream.ReadByte(&inputLayoutSize); stream.ReadByte(&inputLayoutSize);
ASSERT(inputLayoutSize <= VERTEX_SHADER_MAX_INPUT_ELEMENTS); ASSERT(inputLayoutSize <= VERTEX_SHADER_MAX_INPUT_ELEMENTS);
D3D11_INPUT_ELEMENT_DESC inputLayoutDesc[VERTEX_SHADER_MAX_INPUT_ELEMENTS];
for (int32 a = 0; a < inputLayoutSize; a++) for (int32 a = 0; a < inputLayoutSize; a++)
{ {
// Read description // Read description
// TODO: maybe use struct and load at once? GPUShaderProgramVS::InputElement inputElement;
stream.ReadByte(&Type); stream.Read(inputElement);
stream.ReadByte(&Index);
stream.ReadByte(&Format);
stream.ReadByte(&InputSlot);
stream.ReadUint32(&AlignedByteOffset);
stream.ReadByte(&InputSlotClass);
stream.ReadUint32(&InstanceDataStepRate);
// Get semantic name // Get semantic name
const char* semanticName = nullptr; const char* semanticName = nullptr;
// TODO: maybe use enum+mapping ? // TODO: maybe use enum+mapping ?
switch (Type) switch (inputElement.Type)
{ {
case 1: case 1:
semanticName = "POSITION"; semanticName = "POSITION";
@@ -70,7 +59,7 @@ GPUShaderProgram* GPUShaderDX11::CreateGPUShaderProgram(ShaderStage type, const
semanticName = "BLENDWEIGHT"; semanticName = "BLENDWEIGHT";
break; break;
default: default:
LOG(Fatal, "Invalid vertex shader element semantic type: {0}", Type); LOG(Fatal, "Invalid vertex shader element semantic type: {0}", inputElement.Type);
break; break;
} }
@@ -78,12 +67,12 @@ GPUShaderProgram* GPUShaderDX11::CreateGPUShaderProgram(ShaderStage type, const
inputLayoutDesc[a] = inputLayoutDesc[a] =
{ {
semanticName, semanticName,
static_cast<UINT>(Index), static_cast<UINT>(inputElement.Index),
static_cast<DXGI_FORMAT>(Format), static_cast<DXGI_FORMAT>(inputElement.Format),
static_cast<UINT>(InputSlot), static_cast<UINT>(inputElement.InputSlot),
static_cast<UINT>(AlignedByteOffset), static_cast<UINT>(inputElement.AlignedByteOffset),
static_cast<D3D11_INPUT_CLASSIFICATION>(InputSlotClass), static_cast<D3D11_INPUT_CLASSIFICATION>(inputElement.InputSlotClass),
static_cast<UINT>(InstanceDataStepRate) static_cast<UINT>(inputElement.InstanceDataStepRate)
}; };
} }

View File

@@ -20,32 +20,21 @@ GPUShaderProgram* GPUShaderDX12::CreateGPUShaderProgram(ShaderStage type, const
{ {
case ShaderStage::Vertex: 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) // Load Input Layout (it may be empty)
byte inputLayoutSize; byte inputLayoutSize;
stream.ReadByte(&inputLayoutSize); stream.ReadByte(&inputLayoutSize);
ASSERT(inputLayoutSize <= VERTEX_SHADER_MAX_INPUT_ELEMENTS); ASSERT(inputLayoutSize <= VERTEX_SHADER_MAX_INPUT_ELEMENTS);
D3D12_INPUT_ELEMENT_DESC inputLayout[VERTEX_SHADER_MAX_INPUT_ELEMENTS];
for (int32 a = 0; a < inputLayoutSize; a++) for (int32 a = 0; a < inputLayoutSize; a++)
{ {
// Read description // Read description
// TODO: maybe use struct and load at once? GPUShaderProgramVS::InputElement inputElement;
stream.ReadByte(&Type); stream.Read(inputElement);
stream.ReadByte(&Index);
stream.ReadByte(&Format);
stream.ReadByte(&InputSlot);
stream.ReadUint32(&AlignedByteOffset);
stream.ReadByte(&InputSlotClass);
stream.ReadUint32(&InstanceDataStepRate);
// Get semantic name // Get semantic name
const char* semanticName = nullptr; const char* semanticName = nullptr;
// TODO: maybe use enum+mapping ? // TODO: maybe use enum+mapping ?
switch (Type) switch (inputElement.Type)
{ {
case 1: case 1:
semanticName = "POSITION"; semanticName = "POSITION";
@@ -75,7 +64,7 @@ GPUShaderProgram* GPUShaderDX12::CreateGPUShaderProgram(ShaderStage type, const
semanticName = "BLENDWEIGHT"; semanticName = "BLENDWEIGHT";
break; break;
default: default:
LOG(Fatal, "Invalid vertex shader element semantic type: {0}", Type); LOG(Fatal, "Invalid vertex shader element semantic type: {0}", inputElement.Type);
break; break;
} }
@@ -83,12 +72,12 @@ GPUShaderProgram* GPUShaderDX12::CreateGPUShaderProgram(ShaderStage type, const
inputLayout[a] = inputLayout[a] =
{ {
semanticName, semanticName,
static_cast<UINT>(Index), static_cast<UINT>(inputElement.Index),
static_cast<DXGI_FORMAT>(Format), static_cast<DXGI_FORMAT>(inputElement.Format),
static_cast<UINT>(InputSlot), static_cast<UINT>(inputElement.InputSlot),
static_cast<UINT>(AlignedByteOffset), static_cast<UINT>(inputElement.AlignedByteOffset),
static_cast<D3D12_INPUT_CLASSIFICATION>(InputSlotClass), static_cast<D3D12_INPUT_CLASSIFICATION>(inputElement.InputSlotClass),
static_cast<UINT>(InstanceDataStepRate) static_cast<UINT>(inputElement.InstanceDataStepRate)
}; };
} }

View File

@@ -14,9 +14,9 @@
#include "Engine/Graphics/PixelFormatExtensions.h" #include "Engine/Graphics/PixelFormatExtensions.h"
#if PLATFORM_DESKTOP #if PLATFORM_DESKTOP
#define VULKAN_UNIFORM_RING_BUFFER_SIZE 24 * 1024 * 1024 #define VULKAN_UNIFORM_RING_BUFFER_SIZE (24 * 1024 * 1024)
#else #else
#define VULKAN_UNIFORM_RING_BUFFER_SIZE 8 * 1024 * 1024 #define VULKAN_UNIFORM_RING_BUFFER_SIZE (8 * 1024 * 1024)
#endif #endif
UniformBufferUploaderVulkan::UniformBufferUploaderVulkan(GPUDeviceVulkan* device) UniformBufferUploaderVulkan::UniformBufferUploaderVulkan(GPUDeviceVulkan* device)
@@ -153,10 +153,6 @@ GPUShaderProgram* GPUShaderVulkan::CreateGPUShaderProgram(ShaderStage type, cons
vertexBindingDescriptions[i].inputRate = VK_VERTEX_INPUT_RATE_VERTEX; 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) // Load Input Layout (it may be empty)
byte inputLayoutSize; byte inputLayoutSize;
stream.ReadByte(&inputLayoutSize); stream.ReadByte(&inputLayoutSize);
@@ -167,32 +163,26 @@ GPUShaderProgram* GPUShaderVulkan::CreateGPUShaderProgram(ShaderStage type, cons
for (int32 a = 0; a < inputLayoutSize; a++) for (int32 a = 0; a < inputLayoutSize; a++)
{ {
// Read description // Read description
// TODO: maybe use struct and load at once? GPUShaderProgramVS::InputElement inputElement;
stream.ReadByte(&Type); stream.Read(inputElement);
stream.ReadByte(&Index);
stream.ReadByte(&Format);
stream.ReadByte(&InputSlot);
stream.ReadUint32(&AlignedByteOffset);
stream.ReadByte(&InputSlotClass);
stream.ReadUint32(&InstanceDataStepRate);
const auto size = PixelFormatExtensions::SizeInBytes((PixelFormat)Format); const auto size = PixelFormatExtensions::SizeInBytes((PixelFormat)inputElement.Format);
if (AlignedByteOffset != INPUT_LAYOUT_ELEMENT_ALIGN) if (inputElement.AlignedByteOffset != INPUT_LAYOUT_ELEMENT_ALIGN)
offset = AlignedByteOffset; offset = inputElement.AlignedByteOffset;
auto& vertexBindingDescription = vertexBindingDescriptions[InputSlot]; auto& vertexBindingDescription = vertexBindingDescriptions[inputElement.InputSlot];
vertexBindingDescription.binding = InputSlot; vertexBindingDescription.binding = inputElement.InputSlot;
vertexBindingDescription.stride = Math::Max(vertexBindingDescription.stride, (uint32_t)(offset + size)); 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; vertexBindingDescription.inputRate = inputElement.InputSlotClass == INPUT_LAYOUT_ELEMENT_PER_VERTEX_DATA ? VK_VERTEX_INPUT_RATE_VERTEX : VK_VERTEX_INPUT_RATE_INSTANCE;
ASSERT(InstanceDataStepRate == 0 || InstanceDataStepRate == 1); ASSERT(inputElement.InstanceDataStepRate == 0 || inputElement.InstanceDataStepRate == 1);
auto& vertexAttributeDescription = vertexAttributeDescriptions[a]; auto& vertexAttributeDescription = vertexAttributeDescriptions[a];
vertexAttributeDescription.location = a; vertexAttributeDescription.location = a;
vertexAttributeDescription.binding = InputSlot; vertexAttributeDescription.binding = inputElement.InputSlot;
vertexAttributeDescription.format = RenderToolsVulkan::ToVulkanFormat((PixelFormat)Format); vertexAttributeDescription.format = RenderToolsVulkan::ToVulkanFormat((PixelFormat)inputElement.Format);
vertexAttributeDescription.offset = offset; vertexAttributeDescription.offset = offset;
bindingsCount = Math::Max(bindingsCount, (uint32)InputSlot + 1); bindingsCount = Math::Max(bindingsCount, (uint32)inputElement.InputSlot + 1);
offset += size; offset += size;
} }

View File

@@ -416,7 +416,7 @@ public:
} }
// Load scene // Load scene
if (Level::loadScene(SceneAsset.Get())) if (Level::loadScene(SceneAsset))
{ {
LOG(Error, "Failed to deserialize scene {0}", SceneId); LOG(Error, "Failed to deserialize scene {0}", SceneId);
CallSceneEvent(SceneEventType::OnSceneLoadError, nullptr, SceneId); CallSceneEvent(SceneEventType::OnSceneLoadError, nullptr, SceneId);
@@ -816,40 +816,10 @@ bool LevelImpl::unloadScenes()
return false; return false;
} }
bool Level::loadScene(const Guid& sceneId)
{
const auto sceneAsset = Content::LoadAsync<JsonAsset>(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) bool Level::loadScene(JsonAsset* sceneAsset)
{ {
// Keep reference to the asset (prevent unloading during action) // Keep reference to the asset (prevent unloading during action)
AssetReference<JsonAsset> ref = sceneAsset; AssetReference<JsonAsset> ref = sceneAsset;
// Wait for loaded
if (sceneAsset == nullptr || sceneAsset->WaitForLoaded()) if (sceneAsset == nullptr || sceneAsset->WaitForLoaded())
{ {
LOG(Error, "Cannot load scene asset."); LOG(Error, "Cannot load scene asset.");
@@ -879,6 +849,7 @@ bool Level::loadScene(const BytesContainer& sceneData, Scene** outScene)
return true; return true;
} }
ScopeLock lock(ScenesLock);
return loadScene(document, outScene); return loadScene(document, outScene);
} }
@@ -975,6 +946,7 @@ bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** ou
SceneObject** objects = sceneObjects->Get(); SceneObject** objects = sceneObjects->Get();
if (context.Async) 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) JobSystem::Execute([&](int32 i)
{ {
i++; // Start from 1. at index [0] was scene 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 else
SceneObjectsFactory::HandleObjectDeserializationError(stream); SceneObjectsFactory::HandleObjectDeserializationError(stream);
}, objectsCount - 1); }, objectsCount - 1);
ScenesLock.Lock();
} }
else else
{ {
@@ -1330,6 +1303,7 @@ bool Level::LoadScene(const Guid& id)
} }
// Load scene // Load scene
ScopeLock lock(ScenesLock);
if (loadScene(sceneAsset)) if (loadScene(sceneAsset))
{ {
LOG(Error, "Failed to deserialize scene {0}", id); LOG(Error, "Failed to deserialize scene {0}", id);

View File

@@ -541,8 +541,8 @@ private:
}; };
static void callActorEvent(ActorEventType eventType, Actor* a, Actor* b); 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(JsonAsset* sceneAsset);
static bool loadScene(const BytesContainer& sceneData, Scene** outScene = nullptr); static bool loadScene(const BytesContainer& sceneData, Scene** outScene = nullptr);
static bool loadScene(rapidjson_flax::Document& document, Scene** outScene = nullptr); static bool loadScene(rapidjson_flax::Document& document, Scene** outScene = nullptr);

View File

@@ -39,7 +39,7 @@ PrefabManagerService PrefabManagerServiceInstance;
Actor* PrefabManager::SpawnPrefab(Prefab* prefab) Actor* PrefabManager::SpawnPrefab(Prefab* prefab)
{ {
Actor* parent = Level::Scenes.Count() != 0 ? Level::Scenes.Get()[0] : nullptr; 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) 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) 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<Guid, const void*>* objectsCache, bool withSynchronization) Actor* PrefabManager::SpawnPrefab(Prefab* prefab, Actor* parent, Dictionary<Guid, const void*>* 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<Guid, const void*>* objectsCache, bool withSynchronization) Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Transform& transform, Actor* parent, Dictionary<Guid, const void*>* objectsCache, bool withSynchronization)
@@ -191,7 +191,7 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Transform& transform, Ac
parent->Children.Add(root); parent->Children.Add(root);
// Move root to the right location // Move root to the right location
if (transform != Transform::Identity) if (transform.Translation != Vector3::Minimum)
root->SetTransform(transform); root->SetTransform(transform);
// Link actors hierarchy // Link actors hierarchy

View File

@@ -16,6 +16,7 @@
#include "Engine/Threading/ThreadLocal.h" #include "Engine/Threading/ThreadLocal.h"
#if !BUILD_RELEASE || USE_EDITOR #if !BUILD_RELEASE || USE_EDITOR
#include "Engine/Level/Level.h" #include "Engine/Level/Level.h"
#include "Engine/Threading/Threading.h"
#endif #endif
SceneObjectsFactory::Context::Context(ISerializeModifier* modifier) SceneObjectsFactory::Context::Context(ISerializeModifier* modifier)
@@ -279,9 +280,9 @@ void SceneObjectsFactory::HandleObjectDeserializationError(const ISerializable::
#if USE_EDITOR #if USE_EDITOR
// Add dummy script // Add dummy script
auto* dummyScript = parent->AddScript<MissingScript>(); auto* dummyScript = parent->AddScript<MissingScript>();
const auto parentIdMember = value.FindMember("TypeName"); const auto typeNameMember = value.FindMember("TypeName");
if (parentIdMember != value.MemberEnd() && parentIdMember->value.IsString()) if (typeNameMember != value.MemberEnd() && typeNameMember->value.IsString())
dummyScript->MissingTypeName = parentIdMember->value.GetString(); dummyScript->MissingTypeName = typeNameMember->value.GetString();
dummyScript->Data = MoveTemp(bufferStr); dummyScript->Data = MoveTemp(bufferStr);
#endif #endif
LOG(Warning, "Parent actor of the missing object: {0}", parent->GetName()); LOG(Warning, "Parent actor of the missing object: {0}", parent->GetName());

View File

@@ -7,6 +7,7 @@
#include "Engine/Level/Level.h" #include "Engine/Level/Level.h"
#include "Engine/Level/Scene/Scene.h" #include "Engine/Level/Scene/Scene.h"
#include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Threading/Threading.h"
#include <ThirdParty/recastnavigation/DetourCrowd.h> #include <ThirdParty/recastnavigation/DetourCrowd.h>
NavCrowd::NavCrowd(const SpawnParams& params) NavCrowd::NavCrowd(const SpawnParams& params)

View File

@@ -5,7 +5,7 @@
/// <summary> /// <summary>
/// Current GPU particles emitter shader version. /// Current GPU particles emitter shader version.
/// </summary> /// </summary>
#define PARTICLE_GPU_GRAPH_VERSION 9 #define PARTICLE_GPU_GRAPH_VERSION 10
#if COMPILE_WITH_PARTICLE_GPU_GRAPH #if COMPILE_WITH_PARTICLE_GPU_GRAPH

View File

@@ -284,6 +284,7 @@ void ParticleEffect::UpdateSimulation(bool singleFrame)
void ParticleEffect::Play() void ParticleEffect::Play()
{ {
_isPlaying = true; _isPlaying = true;
_isStopped = false;
} }
void ParticleEffect::Pause() void ParticleEffect::Pause()
@@ -293,6 +294,7 @@ void ParticleEffect::Pause()
void ParticleEffect::Stop() void ParticleEffect::Stop()
{ {
_isStopped = true;
_isPlaying = false; _isPlaying = false;
ResetSimulation(); ResetSimulation();
} }
@@ -448,7 +450,7 @@ void ParticleEffect::Update()
void ParticleEffect::UpdateExecuteInEditor() void ParticleEffect::UpdateExecuteInEditor()
{ {
// Auto-play in Editor // Auto-play in Editor
if (!Editor::IsPlayMode) if (!Editor::IsPlayMode && !_isStopped)
{ {
_isPlaying = true; _isPlaying = true;
Update(); Update();

View File

@@ -133,7 +133,7 @@ public:
/// <summary> /// <summary>
/// The particle system instance that plays the particles simulation in the game. /// The particle system instance that plays the particles simulation in the game.
/// </summary> /// </summary>
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 class FLAXENGINE_API ParticleEffect : public Actor
{ {
DECLARE_SCENE_OBJECT(ParticleEffect); DECLARE_SCENE_OBJECT(ParticleEffect);
@@ -185,6 +185,7 @@ private:
Array<ParticleEffectParameter> _parameters; // Cached for scripting API Array<ParticleEffectParameter> _parameters; // Cached for scripting API
Array<ParameterOverride> _parametersOverrides; // Cached parameter modifications to be applied to the parameters Array<ParameterOverride> _parametersOverrides; // Cached parameter modifications to be applied to the parameters
bool _isPlaying = false; bool _isPlaying = false;
bool _isStopped = false;
public: public:
/// <summary> /// <summary>

View File

@@ -1318,6 +1318,8 @@ void ParticlesSystem::Job(int32 index)
emitterInstance.Buffer = nullptr; emitterInstance.Buffer = nullptr;
} }
} }
// Stop playing effect.
effect->Stop();
return; return;
} }
} }
@@ -1332,7 +1334,8 @@ void ParticlesSystem::Job(int32 index)
auto emitter = particleSystem->Emitters[track.AsEmitter.Index].Get(); auto emitter = particleSystem->Emitters[track.AsEmitter.Index].Get();
auto& data = instance.Emitters[track.AsEmitter.Index]; auto& data = instance.Emitters[track.AsEmitter.Index];
ASSERT(emitter && emitter->IsLoaded()); 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); PROFILE_CPU_ASSET(emitter);
// Calculate new time position // Calculate new time position

View File

@@ -95,14 +95,17 @@ void Cloth::SetFabric(const FabricSettings& value)
void Cloth::Rebuild() void Cloth::Rebuild()
{ {
#if WITH_CLOTH #if WITH_CLOTH
// Remove old if (_cloth)
if (IsDuringPlay()) {
PhysicsBackend::RemoveCloth(GetPhysicsScene()->GetPhysicsScene(), _cloth); // Remove old
DestroyCloth(); if (IsDuringPlay())
PhysicsBackend::RemoveCloth(GetPhysicsScene()->GetPhysicsScene(), _cloth);
DestroyCloth();
}
// Create new // Create new
CreateCloth(); CreateCloth();
if (IsDuringPlay()) if (IsDuringPlay() && _cloth)
PhysicsBackend::AddCloth(GetPhysicsScene()->GetPhysicsScene(), _cloth); PhysicsBackend::AddCloth(GetPhysicsScene()->GetPhysicsScene(), _cloth);
#endif #endif
} }

View File

@@ -232,13 +232,13 @@ private:
public: public:
/// <summary> /// <summary>
/// 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.
/// </summary> /// </summary>
API_PROPERTY(Attributes="EditorOrder(0), EditorDisplay(\"Cloth\")") API_PROPERTY(Attributes="EditorOrder(0), EditorDisplay(\"Cloth\")")
ModelInstanceActor::MeshReference GetMesh() const; ModelInstanceActor::MeshReference GetMesh() const;
/// <summary> /// <summary>
/// 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.
/// </summary> /// </summary>
API_PROPERTY() void SetMesh(const ModelInstanceActor::MeshReference& value); API_PROPERTY() void SetMesh(const ModelInstanceActor::MeshReference& value);

View File

@@ -1,7 +1,6 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#include "PhysicsColliderActor.h" #include "PhysicsColliderActor.h"
#include "Engine/Scripting/Script.h"
#include "RigidBody.h" #include "RigidBody.h"
PhysicsColliderActor::PhysicsColliderActor(const SpawnParams& params) PhysicsColliderActor::PhysicsColliderActor(const SpawnParams& params)

View File

@@ -117,9 +117,7 @@ void SimulationEventCallback::onContact(const PxContactPairHeader& pairHeader, c
{ {
// Skip sending events to removed actors // Skip sending events to removed actors
if (pairHeader.flags & (PxContactPairHeaderFlag::eREMOVED_ACTOR_0 | PxContactPairHeaderFlag::eREMOVED_ACTOR_1)) if (pairHeader.flags & (PxContactPairHeaderFlag::eREMOVED_ACTOR_0 | PxContactPairHeaderFlag::eREMOVED_ACTOR_1))
{
return; return;
}
Collision c; Collision c;
PxContactPairExtraDataIterator j(pairHeader.extraDataStream, pairHeader.extraDataStreamSize); PxContactPairExtraDataIterator j(pairHeader.extraDataStream, pairHeader.extraDataStreamSize);
@@ -193,7 +191,7 @@ void SimulationEventCallback::onContact(const PxContactPairHeader& pairHeader, c
RemovedCollisions.Add(c); RemovedCollisions.Add(c);
} }
} }
ASSERT(!j.nextItemSet()); //ASSERT(!j.nextItemSet());
} }
void SimulationEventCallback::onTrigger(PxTriggerPair* pairs, PxU32 count) void SimulationEventCallback::onTrigger(PxTriggerPair* pairs, PxU32 count)

View File

@@ -48,6 +48,7 @@ CPUInfo Cpu;
String UserLocale; String UserLocale;
double SecondsPerCycle; double SecondsPerCycle;
NSAutoreleasePool* AutoreleasePool = nullptr; NSAutoreleasePool* AutoreleasePool = nullptr;
int32 AutoreleasePoolInterval = 0;
float ApplePlatform::ScreenScale = 1.0f; float ApplePlatform::ScreenScale = 1.0f;
@@ -245,13 +246,40 @@ void ApplePlatform::GetUTCTime(int32& year, int32& month, int32& dayOfWeek, int3
millisecond = time.tv_usec / 1000; millisecond = time.tv_usec / 1000;
} }
#if !BUILD_RELEASE
void ApplePlatform::Log(const StringView& msg) void ApplePlatform::Log(const StringView& msg)
{ {
#if !BUILD_RELEASE && !USE_EDITOR #if !USE_EDITOR
NSLog(@"%s", StringAsANSI<>(*msg, msg.Length()).Get()); NSLog(@"%s", StringAsANSI<>(*msg, msg.Length()).Get());
#endif #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() bool ApplePlatform::Init()
{ {
if (UnixPlatform::Init()) if (UnixPlatform::Init())
@@ -332,9 +360,13 @@ bool ApplePlatform::Init()
void ApplePlatform::Tick() void ApplePlatform::Tick()
{ {
// TODO: do it once every X fames AutoreleasePoolInterval++;
[AutoreleasePool drain]; if (AutoreleasePoolInterval >= 60)
AutoreleasePool = [[NSAutoreleasePool alloc] init]; {
AutoreleasePoolInterval = 0;
[AutoreleasePool drain];
AutoreleasePool = [[NSAutoreleasePool alloc] init];
}
} }
void ApplePlatform::BeforeExit() void ApplePlatform::BeforeExit()
@@ -343,6 +375,11 @@ void ApplePlatform::BeforeExit()
void ApplePlatform::Exit() void ApplePlatform::Exit()
{ {
if (AutoreleasePool)
{
[AutoreleasePool drain];
AutoreleasePool = nullptr;
}
} }
void ApplePlatform::SetHighDpiAwarenessEnabled(bool enable) void ApplePlatform::SetHighDpiAwarenessEnabled(bool enable)
@@ -369,9 +406,7 @@ bool ApplePlatform::GetHasFocus()
if (window->IsFocused()) if (window->IsFocused())
return true; return true;
} }
return false;
// Default to true if has no windows open
return WindowsManager::Windows.IsEmpty();
} }
void ApplePlatform::CreateGuid(Guid& result) void ApplePlatform::CreateGuid(Guid& result)

View File

@@ -79,7 +79,10 @@ public:
static uint64 GetClockFrequency(); static uint64 GetClockFrequency();
static void GetSystemTime(int32& year, int32& month, int32& dayOfWeek, int32& day, int32& hour, int32& minute, int32& second, int32& millisecond); 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); 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 void Log(const StringView& msg);
static bool IsDebuggerPresent();
#endif
static bool Init(); static bool Init();
static void Tick(); static void Tick();
static void BeforeExit(); static void BeforeExit();

View File

@@ -59,9 +59,11 @@ void WindowsManagerService::Update()
// Update windows // Update windows
const float deltaTime = Time::Update.UnscaledDeltaTime.GetTotalSeconds(); const float deltaTime = Time::Update.UnscaledDeltaTime.GetTotalSeconds();
WindowsManager::WindowsLocker.Lock(); WindowsManager::WindowsLocker.Lock();
for (Window* win : WindowsManager::Windows) Array<Window*, InlinedAllocation<32>> windows;
windows.Add(WindowsManager::Windows);
for (Window* win : windows)
{ {
if (win && win->IsVisible()) if (win->IsVisible())
win->OnUpdate(deltaTime); win->OnUpdate(deltaTime);
} }
WindowsManager::WindowsLocker.Unlock(); WindowsManager::WindowsLocker.Unlock();
@@ -71,7 +73,8 @@ void WindowsManagerService::Dispose()
{ {
// Close remaining windows // Close remaining windows
WindowsManager::WindowsLocker.Lock(); WindowsManager::WindowsLocker.Lock();
auto windows = WindowsManager::Windows; Array<Window*, InlinedAllocation<32>> windows;
windows.Add(WindowsManager::Windows);
for (Window* win : windows) for (Window* win : windows)
{ {
win->Close(ClosingReason::EngineExit); win->Close(ClosingReason::EngineExit);

View File

@@ -2622,9 +2622,7 @@ bool LinuxPlatform::GetHasFocus()
if (window->IsFocused()) if (window->IsFocused())
return true; return true;
} }
return false;
// Default to true if has no windows open
return WindowsManager::Windows.IsEmpty();
} }
bool LinuxPlatform::CanOpenUrl(const StringView& url) bool LinuxPlatform::CanOpenUrl(const StringView& url)

View File

@@ -4,7 +4,14 @@
#include "../Window.h" #include "../Window.h"
#include "Engine/Platform/Apple/AppleUtils.h" #include "Engine/Platform/Apple/AppleUtils.h"
#include "Engine/Platform/WindowsManager.h"
#include "Engine/Platform/IGuiData.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/Core/Log.h"
#include "Engine/Input/Input.h" #include "Engine/Input/Input.h"
#include "Engine/Input/Mouse.h" #include "Engine/Input/Mouse.h"
@@ -14,6 +21,37 @@
#include <AppKit/AppKit.h> #include <AppKit/AppKit.h>
#include <QuartzCore/CAMetalLayer.h> #include <QuartzCore/CAMetalLayer.h>
#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) KeyboardKeys GetKey(NSEvent* event)
{ {
switch ([event keyCode]) 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 // Handle resizing to be sure that content has valid size when window was resized
[self windowDidResize:notification]; [self windowDidResize:notification];
if (IsWindowInvalid(Window)) return;
Window->OnGotFocus(); Window->OnGotFocus();
} }
- (void)windowDidResignKey:(NSNotification*)notification - (void)windowDidResignKey:(NSNotification*)notification
{ {
if (IsWindowInvalid(Window)) return;
Window->OnLostFocus(); Window->OnLostFocus();
} }
- (void)windowWillClose:(NSNotification*)notification - (void)windowWillClose:(NSNotification*)notification
{ {
[self setDelegate: nil]; [self setDelegate: nil];
if (IsWindowInvalid(Window)) return;
Window->Close(ClosingReason::User); Window->Close(ClosingReason::User);
} }
@@ -311,7 +352,7 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r)
@end @end
@interface MacViewImpl : NSView @interface MacViewImpl : NSView <NSDraggingSource, NSPasteboardItemDataProvider>
{ {
MacWindow* Window; MacWindow* Window;
NSTrackingArea* TrackingArea; NSTrackingArea* TrackingArea;
@@ -375,6 +416,7 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r)
- (void)keyDown:(NSEvent*)event - (void)keyDown:(NSEvent*)event
{ {
if (IsWindowInvalid(Window)) return;
KeyboardKeys key = GetKey(event); KeyboardKeys key = GetKey(event);
if (key != KeyboardKeys::None) if (key != KeyboardKeys::None)
Input::Keyboard->OnKeyDown(key, Window); Input::Keyboard->OnKeyDown(key, Window);
@@ -405,6 +447,7 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r)
- (void)keyUp:(NSEvent*)event - (void)keyUp:(NSEvent*)event
{ {
if (IsWindowInvalid(Window)) return;
KeyboardKeys key = GetKey(event); KeyboardKeys key = GetKey(event);
if (key != KeyboardKeys::None) if (key != KeyboardKeys::None)
Input::Keyboard->OnKeyUp(key, Window); Input::Keyboard->OnKeyUp(key, Window);
@@ -412,6 +455,7 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r)
- (void)flagsChanged:(NSEvent*)event - (void)flagsChanged:(NSEvent*)event
{ {
if (IsWindowInvalid(Window)) return;
int32 modMask; int32 modMask;
int32 keyCode = [event keyCode]; int32 keyCode = [event keyCode];
if (keyCode == 0x36 || keyCode == 0x37) if (keyCode == 0x36 || keyCode == 0x37)
@@ -437,6 +481,7 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r)
- (void)scrollWheel:(NSEvent*)event - (void)scrollWheel:(NSEvent*)event
{ {
if (IsWindowInvalid(Window)) return;
Float2 mousePos = GetMousePosition(Window, event); Float2 mousePos = GetMousePosition(Window, event);
double deltaX = [event scrollingDeltaX]; double deltaX = [event scrollingDeltaX];
double deltaY = [event scrollingDeltaY]; double deltaY = [event scrollingDeltaY];
@@ -451,32 +496,39 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r)
- (void)mouseMoved:(NSEvent*)event - (void)mouseMoved:(NSEvent*)event
{ {
if (IsWindowInvalid(Window)) return;
Float2 mousePos = GetMousePosition(Window, event); 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; return;
Input::Mouse->OnMouseMove(Window->ClientToScreen(mousePos), Window); Input::Mouse->OnMouseMove(Window->ClientToScreen(mousePos), Window);
} }
- (void)mouseEntered:(NSEvent*)event - (void)mouseEntered:(NSEvent*)event
{ {
if (IsWindowInvalid(Window)) return;
IsMouseOver = true; IsMouseOver = true;
Window->SetIsMouseOver(true); Window->SetIsMouseOver(true);
} }
- (void)mouseExited:(NSEvent*)event - (void)mouseExited:(NSEvent*)event
{ {
if (IsWindowInvalid(Window)) return;
IsMouseOver = false; IsMouseOver = false;
Window->SetIsMouseOver(false); Window->SetIsMouseOver(false);
} }
- (void)mouseDown:(NSEvent*)event - (void)mouseDown:(NSEvent*)event
{ {
if (IsWindowInvalid(Window)) return;
Float2 mousePos = GetMousePosition(Window, event); Float2 mousePos = GetMousePosition(Window, event);
mousePos = Window->ClientToScreen(mousePos);
MouseButton mouseButton = MouseButton::Left; MouseButton mouseButton = MouseButton::Left;
if ([event clickCount] == 2) if ([event clickCount] == 2)
Input::Mouse->OnMouseDoubleClick(Window->ClientToScreen(mousePos), mouseButton, Window); Input::Mouse->OnMouseDoubleClick(mousePos, mouseButton, Window);
else else
Input::Mouse->OnMouseDown(Window->ClientToScreen(mousePos), mouseButton, Window); Input::Mouse->OnMouseDown(mousePos, mouseButton, Window);
} }
- (void)mouseDragged:(NSEvent*)event - (void)mouseDragged:(NSEvent*)event
@@ -486,13 +538,28 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r)
- (void)mouseUp:(NSEvent*)event - (void)mouseUp:(NSEvent*)event
{ {
if (IsWindowInvalid(Window)) return;
Float2 mousePos = GetMousePosition(Window, event); Float2 mousePos = GetMousePosition(Window, event);
mousePos = Window->ClientToScreen(mousePos);
MouseButton mouseButton = MouseButton::Left; 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 - (void)rightMouseDown:(NSEvent*)event
{ {
if (IsWindowInvalid(Window)) return;
Float2 mousePos = GetMousePosition(Window, event); Float2 mousePos = GetMousePosition(Window, event);
MouseButton mouseButton = MouseButton::Right; MouseButton mouseButton = MouseButton::Right;
if ([event clickCount] == 2) if ([event clickCount] == 2)
@@ -508,6 +575,7 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r)
- (void)rightMouseUp:(NSEvent*)event - (void)rightMouseUp:(NSEvent*)event
{ {
if (IsWindowInvalid(Window)) return;
Float2 mousePos = GetMousePosition(Window, event); Float2 mousePos = GetMousePosition(Window, event);
MouseButton mouseButton = MouseButton::Right; MouseButton mouseButton = MouseButton::Right;
Input::Mouse->OnMouseUp(Window->ClientToScreen(mousePos), mouseButton, Window); Input::Mouse->OnMouseUp(Window->ClientToScreen(mousePos), mouseButton, Window);
@@ -515,6 +583,7 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r)
- (void)otherMouseDown:(NSEvent*)event - (void)otherMouseDown:(NSEvent*)event
{ {
if (IsWindowInvalid(Window)) return;
Float2 mousePos = GetMousePosition(Window, event); Float2 mousePos = GetMousePosition(Window, event);
MouseButton mouseButton; MouseButton mouseButton;
switch ([event buttonNumber]) switch ([event buttonNumber])
@@ -544,6 +613,7 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r)
- (void)otherMouseUp:(NSEvent*)event - (void)otherMouseUp:(NSEvent*)event
{ {
if (IsWindowInvalid(Window)) return;
Float2 mousePos = GetMousePosition(Window, event); Float2 mousePos = GetMousePosition(Window, event);
MouseButton mouseButton; MouseButton mouseButton;
switch ([event buttonNumber]) switch ([event buttonNumber])
@@ -565,6 +635,7 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r)
- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender - (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender
{ {
if (IsWindowInvalid(Window)) return NSDragOperationNone;
Float2 mousePos; Float2 mousePos;
MacDropData dropData; MacDropData dropData;
GetDragDropData(Window, sender, mousePos, dropData); GetDragDropData(Window, sender, mousePos, dropData);
@@ -580,6 +651,7 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r)
- (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)sender - (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)sender
{ {
if (IsWindowInvalid(Window)) return NSDragOperationNone;
Float2 mousePos; Float2 mousePos;
MacDropData dropData; MacDropData dropData;
GetDragDropData(Window, sender, mousePos, dropData); GetDragDropData(Window, sender, mousePos, dropData);
@@ -590,6 +662,7 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r)
- (BOOL)performDragOperation:(id<NSDraggingInfo>)sender - (BOOL)performDragOperation:(id<NSDraggingInfo>)sender
{ {
if (IsWindowInvalid(Window)) return NO;
Float2 mousePos; Float2 mousePos;
MacDropData dropData; MacDropData dropData;
GetDragDropData(Window, sender, mousePos, dropData); GetDragDropData(Window, sender, mousePos, dropData);
@@ -600,9 +673,38 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r)
- (void)draggingExited:(id<NSDraggingInfo>)sender - (void)draggingExited:(id<NSDraggingInfo>)sender
{ {
if (IsWindowInvalid(Window)) return;
Window->OnDragLeave(); 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 @end
MacWindow::MacWindow(const CreateWindowSettings& settings) MacWindow::MacWindow(const CreateWindowSettings& settings)
@@ -624,6 +726,8 @@ MacWindow::MacWindow(const CreateWindowSettings& settings)
{ {
styleMask |= NSWindowStyleMaskBorderless; styleMask |= NSWindowStyleMaskBorderless;
} }
if (settings.Fullscreen)
styleMask |= NSWindowStyleMaskFullScreen;
if (settings.HasBorder) if (settings.HasBorder)
{ {
styleMask |= NSWindowStyleMaskTitled; styleMask |= NSWindowStyleMaskTitled;
@@ -650,9 +754,11 @@ MacWindow::MacWindow(const CreateWindowSettings& settings)
[window setMaxSize:NSMakeSize(settings.MaximumSize.X, settings.MaximumSize.Y)]; [window setMaxSize:NSMakeSize(settings.MaximumSize.X, settings.MaximumSize.Y)];
[window setOpaque:!settings.SupportsTransparency]; [window setOpaque:!settings.SupportsTransparency];
[window setContentView:view]; [window setContentView:view];
[window setAcceptsMouseMovedEvents:YES]; if (settings.AllowInput)
[window setAcceptsMouseMovedEvents:YES];
[window setDelegate:window]; [window setDelegate:window];
_window = window; _window = window;
_view = view;
if (settings.AllowDragAndDrop) if (settings.AllowDragAndDrop)
{ {
[view registerForDraggedTypes:@[NSPasteboardTypeFileURL, NSPasteboardTypeString]]; [view registerForDraggedTypes:@[NSPasteboardTypeFileURL, NSPasteboardTypeString]];
@@ -664,8 +770,6 @@ MacWindow::MacWindow(const CreateWindowSettings& settings)
layer.contentsScale = screenScale; layer.contentsScale = screenScale;
// TODO: impl Parent for MacWindow // TODO: impl Parent for MacWindow
// TODO: impl StartPosition for MacWindow
// TODO: impl Fullscreen for MacWindow
// TODO: impl ShowInTaskbar for MacWindow // TODO: impl ShowInTaskbar for MacWindow
// TODO: impl IsTopmost for MacWindow // TODO: impl IsTopmost for MacWindow
} }
@@ -676,6 +780,7 @@ MacWindow::~MacWindow()
[window close]; [window close];
[window release]; [window release];
_window = nullptr; _window = nullptr;
_view = nullptr;
} }
void MacWindow::CheckForResize(float width, float height) void MacWindow::CheckForResize(float width, float height)
@@ -699,7 +804,6 @@ void MacWindow::SetIsMouseOver(bool value)
// Refresh cursor typet // Refresh cursor typet
SetCursor(CursorType::Default); SetCursor(CursorType::Default);
SetCursor(cursor); SetCursor(cursor);
} }
else else
{ {
@@ -714,6 +818,22 @@ void* MacWindow::GetNativePtr() const
return _window; 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() void MacWindow::Show()
{ {
if (!_visible) if (!_visible)
@@ -728,7 +848,10 @@ void MacWindow::Show()
// Show // Show
NSWindow* window = (NSWindow*)_window; NSWindow* window = (NSWindow*)_window;
[window makeKeyAndOrderFront:window]; if (_settings.AllowInput)
[window makeKeyAndOrderFront:window];
else
[window orderFront:window];
if (_settings.ActivateWhenFirstShown) if (_settings.ActivateWhenFirstShown)
[NSApp activateIgnoringOtherApps:YES]; [NSApp activateIgnoringOtherApps:YES];
_focused = true; _focused = true;
@@ -746,7 +869,7 @@ void MacWindow::Hide()
// Hide // Hide
NSWindow* window = (NSWindow*)_window; NSWindow* window = (NSWindow*)_window;
[window orderOut:nil]; [window orderOut:window];
// Base // Base
WindowBase::Hide(); WindowBase::Hide();
@@ -782,14 +905,9 @@ void MacWindow::Restore()
[window zoom:nil]; [window zoom:nil];
} }
bool MacWindow::IsClosed() const
{
return _window != nullptr;
}
bool MacWindow::IsForegroundWindow() const bool MacWindow::IsForegroundWindow() const
{ {
return Platform::GetHasFocus() && IsFocused(); return IsFocused() && Platform::GetHasFocus();
} }
void MacWindow::BringToFront(bool force) void MacWindow::BringToFront(bool force)
@@ -808,14 +926,13 @@ void MacWindow::SetClientBounds(const Rectangle& clientArea)
NSWindow* window = (NSWindow*)_window; NSWindow* window = (NSWindow*)_window;
if (!window) if (!window)
return; return;
const float screenScale = MacPlatform::ScreenScale;
NSRect oldRect = [window frame]; 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 = [window frameRectForContentRect:newRect];
//newRect.origin.x = oldRect.origin.x; Float2 pos = AppleUtils::PosToCoca(clientArea.Location) / screenScale;
//newRect.origin.y = NSMaxY(oldRect) - newRect.size.height;
Float2 pos = AppleUtils::PosToCoca(clientArea.Location);
Float2 titleSize = GetWindowTitleSize(this); Float2 titleSize = GetWindowTitleSize(this);
newRect.origin.x = pos.X + titleSize.X; newRect.origin.x = pos.X + titleSize.X;
newRect.origin.y = pos.Y - newRect.size.height + titleSize.Y; 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) DragDropEffect MacWindow::DoDragDrop(const StringView& data)
{ {
// TODO: implement using beginDraggingSession and NSDraggingSource NSWindow* window = (NSWindow*)_window;
return DragDropEffect::None; 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<DoDragDropJob>();
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) void MacWindow::SetCursor(CursorType type)

View File

@@ -6,6 +6,7 @@
#include "Engine/Platform/Base/WindowBase.h" #include "Engine/Platform/Base/WindowBase.h"
#include "Engine/Platform/Platform.h" #include "Engine/Platform/Platform.h"
#include "Engine/Core/Math/Vector2.h"
/// <summary> /// <summary>
/// Implementation of the window class for Mac platform. /// Implementation of the window class for Mac platform.
@@ -13,28 +14,32 @@
class FLAXENGINE_API MacWindow : public WindowBase class FLAXENGINE_API MacWindow : public WindowBase
{ {
private: private:
void* _window = nullptr;
void* _window; void* _view = nullptr;
bool _isMouseOver = false; bool _isMouseOver = false;
Float2 _mouseTrackPos = Float2::Minimum;
String _dragText;
public: public:
MacWindow(const CreateWindowSettings& settings); MacWindow(const CreateWindowSettings& settings);
~MacWindow(); ~MacWindow();
void CheckForResize(float width, float height); void CheckForResize(float width, float height);
void SetIsMouseOver(bool value); void SetIsMouseOver(bool value);
const String& GetDragText() const
{
return _dragText;
}
public: public:
// [WindowBase] // [WindowBase]
void* GetNativePtr() const override; void* GetNativePtr() const override;
void OnUpdate(float dt) override;
void Show() override; void Show() override;
void Hide() override; void Hide() override;
void Minimize() override; void Minimize() override;
void Maximize() override; void Maximize() override;
void Restore() override; void Restore() override;
bool IsClosed() const override;
bool IsForegroundWindow() const override; bool IsForegroundWindow() const override;
void BringToFront(bool force = false) override; void BringToFront(bool force = false) override;
void SetClientBounds(const Rectangle& clientArea) override; void SetClientBounds(const Rectangle& clientArea) override;
@@ -50,6 +55,8 @@ public:
void Focus() override; void Focus() override;
void SetTitle(const StringView& title) override; void SetTitle(const StringView& title) override;
DragDropEffect DoDragDrop(const StringView& data) override; DragDropEffect DoDragDrop(const StringView& data) override;
void StartTrackingMouse(bool useMouseScreenOffset) override;
void EndTrackingMouse() override;
void SetCursor(CursorType type) override; void SetCursor(CursorType type) override;
}; };

View File

@@ -96,7 +96,11 @@ static bool CreateEndPointFromAddr(sockaddr* addr, NetworkEndPoint& endPoint)
return true; return true;
} }
char strPort[6]; char strPort[6];
#if __APPLE__
snprintf(strPort, sizeof(strPort), "%d", port);
#else
sprintf(strPort, "%d", port); sprintf(strPort, "%d", port);
#endif
endPoint.IPVersion = addr->sa_family == AF_INET6 ? NetworkIPVersion::IPv6 : NetworkIPVersion::IPv4; endPoint.IPVersion = addr->sa_family == AF_INET6 ? NetworkIPVersion::IPv6 : NetworkIPVersion::IPv4;
memcpy(endPoint.Data, addr, size); memcpy(endPoint.Data, addr, size);
return false; return false;

View File

@@ -16,16 +16,13 @@ class FLAXENGINE_API Win32CriticalSection
friend Win32ConditionVariable; friend Win32ConditionVariable;
private: private:
mutable Windows::CRITICAL_SECTION _criticalSection; mutable Windows::CRITICAL_SECTION _criticalSection;
private: private:
Win32CriticalSection(const Win32CriticalSection&); Win32CriticalSection(const Win32CriticalSection&);
Win32CriticalSection& operator=(const Win32CriticalSection&); Win32CriticalSection& operator=(const Win32CriticalSection&);
public: public:
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Win32CriticalSection"/> class. /// Initializes a new instance of the <see cref="Win32CriticalSection"/> class.
/// </summary> /// </summary>
@@ -43,17 +40,12 @@ public:
} }
public: public:
/// <summary> /// <summary>
/// Locks the critical section. /// Locks the critical section.
/// </summary> /// </summary>
void Lock() const void Lock() const
{ {
// Spin first before entering critical section, causing ring-0 transition and context switch Windows::EnterCriticalSection(&_criticalSection);
if (Windows::TryEnterCriticalSection(&_criticalSection) == 0)
{
Windows::EnterCriticalSection(&_criticalSection);
}
} }
/// <summary> /// <summary>

View File

@@ -451,6 +451,7 @@ DialogResult MessageBox::Show(Window* parent, const StringView& text, const Stri
default: default:
break; break;
} }
flags |= MB_TASKMODAL;
// Show dialog // Show dialog
int result = MessageBoxW(parent ? static_cast<HWND>(parent->GetNativePtr()) : nullptr, String(text).GetText(), String(caption).GetText(), flags); int result = MessageBoxW(parent ? static_cast<HWND>(parent->GetNativePtr()) : nullptr, String(text).GetText(), String(caption).GetText(), flags);

View File

@@ -410,7 +410,7 @@ bool iOSWindow::IsClosed() const
bool iOSWindow::IsForegroundWindow() const bool iOSWindow::IsForegroundWindow() const
{ {
return Platform::GetHasFocus() && IsFocused(); return IsFocused() && Platform::GetHasFocus();
} }
void iOSWindow::BringToFront(bool force) void iOSWindow::BringToFront(bool force)

View File

@@ -202,16 +202,14 @@ GPUTexture* DepthOfFieldPass::getDofBokehShape(DepthOfFieldSettings& dofSettings
void DepthOfFieldPass::Render(RenderContext& renderContext, GPUTexture*& frame, GPUTexture*& tmp) 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; return;
auto device = GPUDevice::Instance; auto device = GPUDevice::Instance;
auto context = device->GetMainContext(); auto context = device->GetMainContext();
const auto depthBuffer = renderContext.Buffers->DepthBuffer; const auto depthBuffer = renderContext.Buffers->DepthBuffer;
const auto shader = _shader->GetShader(); 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"); PROFILE_GPU_CPU("Depth Of Field");
context->ResetSR(); context->ResetSR();

View File

@@ -35,7 +35,6 @@ PACK_STRUCT(struct EyeAdaptationData {
void EyeAdaptationPass::Render(RenderContext& renderContext, GPUTexture* colorBuffer) void EyeAdaptationPass::Render(RenderContext& renderContext, GPUTexture* colorBuffer)
{ {
// Cache data
auto device = GPUDevice::Instance; auto device = GPUDevice::Instance;
auto context = device->GetMainContext(); auto context = device->GetMainContext();
auto& view = renderContext.View; 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::ElapsedGameTime.GetTotalSeconds();
const float frameDelta = time - renderContext.Buffers->LastEyeAdaptationTime; const float frameDelta = time - renderContext.Buffers->LastEyeAdaptationTime;
renderContext.Buffers->LastEyeAdaptationTime = 0.0f; renderContext.Buffers->LastEyeAdaptationTime = 0.0f;
if ((view.Flags & ViewFlags::EyeAdaptation) == ViewFlags::None || settings.Mode == EyeAdaptationMode::None || checkIfSkipPass())
// Optionally skip the rendering
if (checkIfSkipPass() || (view.Flags & ViewFlags::EyeAdaptation) == ViewFlags::None || settings.Mode == EyeAdaptationMode::None)
{
return; return;
}
PROFILE_GPU_CPU("Eye Adaptation"); PROFILE_GPU_CPU("Eye Adaptation");

View File

@@ -244,7 +244,7 @@ void MotionBlurPass::RenderDebug(RenderContext& renderContext, GPUTextureView* f
{ {
auto context = GPUDevice::Instance->GetMainContext(); auto context = GPUDevice::Instance->GetMainContext();
const auto motionVectors = renderContext.Buffers->MotionVectors; const auto motionVectors = renderContext.Buffers->MotionVectors;
if (!motionVectors->IsAllocated() || checkIfSkipPass()) if (!motionVectors || !motionVectors->IsAllocated() || checkIfSkipPass())
{ {
context->Draw(frame); context->Draw(frame);
return; return;

View File

@@ -195,7 +195,7 @@ void PostProcessingPass::Render(RenderContext& renderContext, GPUTexture* input,
bool useLensFlares = EnumHasAnyFlags(view.Flags, ViewFlags::LensFlares) && settings.LensFlares.Intensity > 0.0f && useBloom; 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 // 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 // Resources are missing. Do not perform rendering. Just copy raw frame
context->SetViewportAndScissors((float)output->Width(), (float)output->Height()); context->SetViewportAndScissors((float)output->Width(), (float)output->Height());

View File

@@ -1270,7 +1270,11 @@ bool ManagedBinaryModule::InvokeMethod(void* method, const Variant& instance, Sp
// Invoke the method // Invoke the method
MObject* exception = nullptr; 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); MObject* resultObject = withInterfaces ? mMethod->InvokeVirtual((MObject*)mInstance, params, &exception) : mMethod->Invoke(mInstance, params, &exception);
#endif
if (exception) if (exception)
{ {
MException ex(exception); MException ex(exception);

View File

@@ -320,7 +320,7 @@ namespace FlaxEngine
internal static partial Object Internal_Create2(string typeName); internal static partial Object Internal_Create2(string typeName);
[LibraryImport("FlaxEngine", EntryPoint = "ObjectInternal_ManagedInstanceCreated", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(Interop.StringMarshaller))] [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))] [LibraryImport("FlaxEngine", EntryPoint = "ObjectInternal_ManagedInstanceDeleted", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(Interop.StringMarshaller))]
internal static partial void Internal_ManagedInstanceDeleted(IntPtr nativeInstance); internal static partial void Internal_ManagedInstanceDeleted(IntPtr nativeInstance);

View File

@@ -46,6 +46,7 @@
#include <mono/metadata/metadata.h> #include <mono/metadata/metadata.h>
#include <mono/metadata/threads.h> #include <mono/metadata/threads.h>
#include <mono/metadata/reflection.h> #include <mono/metadata/reflection.h>
#include <mono/metadata/mono-gc.h>
#include <mono/metadata/mono-private-unstable.h> #include <mono/metadata/mono-private-unstable.h>
typedef char char_t; typedef char char_t;
#define DOTNET_HOST_MONO_DEBUG 0 #define DOTNET_HOST_MONO_DEBUG 0
@@ -522,7 +523,8 @@ void MCore::GC::FreeMemory(void* ptr, bool coTaskMem)
void MCore::Thread::Attach() 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()) if (!IsInMainThread() && !mono_domain_get())
{ {
mono_thread_attach(MonoDomainHandle); mono_thread_attach(MonoDomainHandle);
@@ -2056,7 +2058,7 @@ bool InitHostfxr()
// Setup debugger // Setup debugger
{ {
int32 debuggerLogLevel = 0; int32 debuggerLogLevel = 0;
if (CommandLine::Options.MonoLog.IsTrue()) if (CommandLine::Options.MonoLog.IsTrue() || DOTNET_HOST_MONO_DEBUG)
{ {
LOG(Info, "Using detailed Mono logging"); LOG(Info, "Using detailed Mono logging");
mono_trace_set_level_string("debug"); mono_trace_set_level_string("debug");
@@ -2139,6 +2141,7 @@ bool InitHostfxr()
LOG(Fatal, "Failed to initialize Mono."); LOG(Fatal, "Failed to initialize Mono.");
return true; return true;
} }
mono_gc_init_finalizer_thread();
// Log info // Log info
char* buildInfo = mono_get_runtime_build_info(); char* buildInfo = mono_get_runtime_build_info();

View File

@@ -460,16 +460,15 @@ bool ShaderCompiler::WriteCustomDataVS(ShaderCompilationContext* context, Shader
auto& element = layout[a]; auto& element = layout[a];
if (!layoutVisible[a]) if (!layoutVisible[a])
continue; continue;
GPUShaderProgramVS::InputElement data;
// TODO: serialize whole struct? data.Type = static_cast<byte>(element.Type);
data.Index = element.Index;
output->WriteByte(static_cast<byte>(element.Type)); data.Format = static_cast<byte>(element.Format);
output->WriteByte(element.Index); data.InputSlot = element.InputSlot;
output->WriteByte(static_cast<byte>(element.Format)); data.AlignedByteOffset = element.AlignedByteOffset;
output->WriteByte(element.InputSlot); data.InputSlotClass = element.InputSlotClass;
output->WriteUint32(element.AlignedByteOffset); data.InstanceDataStepRate = element.InstanceDataStepRate;
output->WriteByte(element.InputSlotClass); output->Write(data);
output->WriteUint32(element.InstanceDataStepRate);
} }
return false; return false;

Binary file not shown.

View File

@@ -222,7 +222,7 @@ ${PBXResourcesGroup}
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
HEADER_SEARCH_PATHS = ${HeaderSearchPaths}; HEADER_SEARCH_PATHS = "${HeaderSearchPaths}";
IPHONEOS_DEPLOYMENT_TARGET = 14.0; IPHONEOS_DEPLOYMENT_TARGET = 14.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
@@ -275,7 +275,7 @@ ${PBXResourcesGroup}
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
HEADER_SEARCH_PATHS = ${HeaderSearchPaths}; HEADER_SEARCH_PATHS = "${HeaderSearchPaths}";
IPHONEOS_DEPLOYMENT_TARGET = 14.0; IPHONEOS_DEPLOYMENT_TARGET = 14.0;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;

View File

@@ -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__ #ifdef __STDC_WANT_SECURE_LIB__
len = sprintf_s(buffer, sizeof(buffer), "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x); 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 #else
len = sprintf(buffer, "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x); len = sprintf(buffer, "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x);
#endif #endif

View File

@@ -1,5 +1,6 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using System.Collections.Generic;
using System.IO; using System.IO;
using Flax.Build; using Flax.Build;
using Flax.Build.NativeCpp; using Flax.Build.NativeCpp;
@@ -62,4 +63,12 @@ public class volk : ThirdPartyModule
Log.ErrorOnce("Missing VulkanSDK.", ref _missingSDKError); Log.ErrorOnce("Missing VulkanSDK.", ref _missingSDKError);
} }
} }
/// <inheritdoc />
public override void GetFilesToDeploy(List<string> files)
{
base.GetFilesToDeploy(files);
files.Add(Path.Combine(FolderPath, "volk.h"));
}
} }

View File

@@ -3135,14 +3135,17 @@ namespace Flax.Build.Bindings
contents.AppendLine("#pragma once"); contents.AppendLine("#pragma once");
contents.AppendLine(); contents.AppendLine();
contents.AppendLine($"#define {binaryModuleNameUpper}_NAME \"{binaryModuleName}\""); contents.AppendLine($"#define {binaryModuleNameUpper}_NAME \"{binaryModuleName}\"");
if (version.Build == -1) if (version.Build <= 0)
contents.AppendLine($"#define {binaryModuleNameUpper}_VERSION Version({version.Major}, {version.Minor})"); 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})"); 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_TEXT \"{version}\"");
contents.AppendLine($"#define {binaryModuleNameUpper}_VERSION_MAJOR {version.Major}"); contents.AppendLine($"#define {binaryModuleNameUpper}_VERSION_MAJOR {version.Major}");
contents.AppendLine($"#define {binaryModuleNameUpper}_VERSION_MINOR {version.Minor}"); contents.AppendLine($"#define {binaryModuleNameUpper}_VERSION_MINOR {version.Minor}");
contents.AppendLine($"#define {binaryModuleNameUpper}_VERSION_BUILD {version.Build}"); 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}_COMPANY \"{project.Company}\"");
contents.AppendLine($"#define {binaryModuleNameUpper}_COPYRIGHT \"{project.Copyright}\""); contents.AppendLine($"#define {binaryModuleNameUpper}_COPYRIGHT \"{project.Copyright}\"");
contents.AppendLine(); contents.AppendLine();

View File

@@ -44,6 +44,12 @@ namespace Flax.Build
/// </summary> /// </summary>
[CommandLine("deployCertPass", "Certificate file password for binaries signing.")] [CommandLine("deployCertPass", "Certificate file password for binaries signing.")]
public static string DeployCertPass; public static string DeployCertPass;
/// <summary>
/// Apple keychain profile name to use for app notarize action (installed locally).
/// </summary>
[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(); Initialize();
if (Configuration.DeployEditor)
{
BuildEditor();
Deployment.Editor.Package();
}
if (Configuration.DeployPlatforms) if (Configuration.DeployPlatforms)
{ {
if (Configuration.BuildPlatforms == null || Configuration.BuildPlatforms.Length == 0) 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) catch (Exception ex)
{ {
@@ -183,6 +189,10 @@ namespace Flax.Deploy
{ {
if (Platform.IsPlatformSupported(platform, architecture)) if (Platform.IsPlatformSupported(platform, architecture))
{ {
Log.Info(string.Empty);
Log.Info($"Build {platform} {architecture} platform");
Log.Info(string.Empty);
foreach (var configuration in Configurations) foreach (var configuration in Configurations)
{ {
FlaxBuild.Build(Globals.EngineRoot, "FlaxGame", platform, architecture, configuration); FlaxBuild.Build(Globals.EngineRoot, "FlaxGame", platform, architecture, configuration);

View File

@@ -127,6 +127,33 @@ namespace Flax.Deploy
// Deploy project // Deploy project
DeployFile(RootPath, OutputPath, "Flax.flaxproj"); 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 // Package Editor into macOS app
if (Platform.BuildTargetPlatform == TargetPlatform.Mac) if (Platform.BuildTargetPlatform == TargetPlatform.Mac)
{ {
@@ -164,12 +191,34 @@ namespace Flax.Deploy
var defaultEditorConfig = "Development"; var defaultEditorConfig = "Development";
var ediotrBinariesPath = Path.Combine(appContentsPath, "Binaries/Editor/Mac", defaultEditorConfig); var ediotrBinariesPath = Path.Combine(appContentsPath, "Binaries/Editor/Mac", defaultEditorConfig);
Utilities.DirectoryCopy(ediotrBinariesPath, appBinariesPath, true, true); 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 // Compress
if (Configuration.DontCompress) if (Configuration.DontCompress)
return; return;
Log.Info(string.Empty); Log.Info(string.Empty);
Log.Info("Compressing editor files..."); Log.Info("Compressing editor files...");
string editorPackageZipPath; string editorPackageZipPath;
@@ -299,6 +348,7 @@ namespace Flax.Deploy
Utilities.Run("strip", "FlaxEditor.dylib", null, dst, Utilities.RunOptions.None); Utilities.Run("strip", "FlaxEditor.dylib", null, dst, Utilities.RunOptions.None);
Utilities.Run("strip", "libMoltenVK.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"));
CodeSign(Path.Combine(dst, "FlaxEditor.dylib")); CodeSign(Path.Combine(dst, "FlaxEditor.dylib"));
CodeSign(Path.Combine(dst, "libMoltenVK.dylib")); CodeSign(Path.Combine(dst, "libMoltenVK.dylib"));

View File

@@ -10,8 +10,13 @@ namespace Flax.Deploy
{ {
public class Platforms public class Platforms
{ {
internal static List<TargetPlatform> PackagedPlatforms;
public static void Package(TargetPlatform platform) public static void Package(TargetPlatform platform)
{ {
if (PackagedPlatforms == null)
PackagedPlatforms = new List<TargetPlatform>();
PackagedPlatforms.Add(platform);
var platformsRoot = Path.Combine(Globals.EngineRoot, "Source", "Platforms"); var platformsRoot = Path.Combine(Globals.EngineRoot, "Source", "Platforms");
Log.Info(string.Empty); Log.Info(string.Empty);
@@ -50,6 +55,20 @@ namespace Flax.Deploy
CodeSign(Path.Combine(binaries, "FlaxGame.exe")); CodeSign(Path.Combine(binaries, "FlaxGame.exe"));
CodeSign(Path.Combine(binaries, "FlaxEngine.CSharp.dll")); 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 // Don't distribute engine deps
Utilities.DirectoryDelete(Path.Combine(dst, "Binaries", "ThirdParty")); Utilities.DirectoryDelete(Path.Combine(dst, "Binaries", "ThirdParty"));

View File

@@ -20,6 +20,7 @@ namespace Flax.Deploy
if (!string.IsNullOrEmpty(Configuration.Compiler)) if (!string.IsNullOrEmpty(Configuration.Compiler))
cmdLine += " -compiler=" + Configuration.Compiler; cmdLine += " -compiler=" + Configuration.Compiler;
Log.Info($"Building {target} for {platform} {architecture} {configuration}...");
int result = Utilities.Run(flaxBuildTool, cmdLine, null, root); int result = Utilities.Run(flaxBuildTool, cmdLine, null, root);
if (result != 0) if (result != 0)
{ {

View File

@@ -53,10 +53,19 @@ namespace Flax.Build.Platforms
/// <param name="signIdenity">App code signing idenity name (from local Mac keychain). Use 'security find-identity -v -p codesigning' to list possible options.</param> /// <param name="signIdenity">App code signing idenity name (from local Mac keychain). Use 'security find-identity -v -p codesigning' to list possible options.</param>
public static void CodeSign(string file, string signIdenity) 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); throw new FileNotFoundException("Missing file to sign.", file);
string cmdLine = string.Format("--force --timestamp -s \"{0}\" \"{1}\"", signIdenity, 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 // Add entitlements file with some settings for the app execution
cmdLine += string.Format(" --entitlements \"{0}\"", Path.Combine(Globals.EngineRoot, "Source/Platforms/Mac/Default.entitlements")); cmdLine += string.Format(" --entitlements \"{0}\"", Path.Combine(Globals.EngineRoot, "Source/Platforms/Mac/Default.entitlements"));

Some files were not shown because too many files have changed in this diff Show More