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_*/
!Source/Engine/Debug
/Source/Platforms/Editor/Linux/Mono/etc/mono/registry
PackageEditor_Cert.command
PackageEditor_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
}
}

View File

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

View File

@@ -15,7 +15,7 @@ if errorlevel 1 goto BuildToolFailed
:: Build bindings for all editor configurations
echo Building C# bindings...
Binaries\Tools\Flax.Build.exe -build -BuildBindingsOnly -arch=x64 -platform=Windows --buildTargets=FlaxEditor,FlaxGame
Binaries\Tools\Flax.Build.exe -build -BuildBindingsOnly -arch=x64 -platform=Windows --buildTargets=FlaxEditor
popd
echo Done!

View File

@@ -14,4 +14,4 @@ bash ./Development/Scripts/Mac/CallBuildTool.sh --genproject "$@"
# Build bindings for all editor configurations
echo Building C# bindings...
# TODO: Detect the correct architecture here
Binaries/Tools/Flax.Build -build -BuildBindingsOnly -arch=ARM64 -platform=Mac --buildTargets=FlaxEditor,FlaxGame
Binaries/Tools/Flax.Build -build -BuildBindingsOnly -arch=ARM64 -platform=Mac --buildTargets=FlaxEditor

View File

@@ -14,4 +14,4 @@ bash ./Development/Scripts/Linux/CallBuildTool.sh --genproject "$@"
# Build bindings for all editor configurations
echo Building C# bindings...
# TODO: Detect the correct architecture here
Binaries/Tools/Flax.Build -build -BuildBindingsOnly -arch=x64 -platform=Linux --buildTargets=FlaxEditor,FlaxGame
Binaries/Tools/Flax.Build -build -BuildBindingsOnly -arch=x64 -platform=Linux --buildTargets=FlaxEditor

View File

@@ -4,7 +4,7 @@
<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#.
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.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -44,11 +44,11 @@ namespace FlaxEditor.GUI.Docking
var mousePos = window.MousePosition;
var previousSize = window.Size;
window.Restore();
window.Position = FlaxEngine.Input.MouseScreenPosition - mousePos * window.Size / previousSize;
window.Position = Platform.MousePosition - mousePos * window.Size / previousSize;
}
// Calculate dragging offset and move window to the destination position
var mouseScreenPosition = FlaxEngine.Input.MouseScreenPosition;
var mouseScreenPosition = Platform.MousePosition;
// If the _toMove window was not focused when initializing this window, the result vector only contains zeros
// and to prevent a failure, we need to perform an update for the drag offset at later time which will be done in the OnMouseMove event handler.
@@ -83,6 +83,7 @@ namespace FlaxEditor.GUI.Docking
// Enable hit window presentation
Proxy.Window.RenderingEnabled = true;
Proxy.Window.Show();
Proxy.Window.Focus();
}
/// <summary>
@@ -113,7 +114,7 @@ namespace FlaxEditor.GUI.Docking
var window = _toMove.Window?.Window;
if (window == null)
return;
var mouse = FlaxEngine.Input.MouseScreenPosition;
var mouse = Platform.MousePosition;
// Move base window
window.Position = mouse - _dragOffset;
@@ -193,7 +194,7 @@ namespace FlaxEditor.GUI.Docking
// Move window to the mouse position (with some offset for caption bar)
var window = (WindowRootControl)toMove.Root;
var mouse = FlaxEngine.Input.MouseScreenPosition;
var mouse = Platform.MousePosition;
window.Window.Position = mouse - new Float2(8, 8);
// Get floating panel
@@ -244,7 +245,7 @@ namespace FlaxEditor.GUI.Docking
private void UpdateRects()
{
// Cache mouse position
_mouse = FlaxEngine.Input.MouseScreenPosition;
_mouse = Platform.MousePosition;
// Check intersection with any dock panel
var uiMouse = _mouse;
@@ -270,15 +271,16 @@ namespace FlaxEditor.GUI.Docking
// Cache dock rectangles
var size = _rectDock.Size;
var offset = _rectDock.Location;
float BorderMargin = 4.0f;
float ProxyHintWindowsSize2 = Proxy.HintWindowsSize * 0.5f;
float centerX = size.X * 0.5f;
float centerY = size.Y * 0.5f;
_rUpper = new Rectangle(centerX - ProxyHintWindowsSize2, BorderMargin, Proxy.HintWindowsSize, Proxy.HintWindowsSize) + offset;
_rBottom = new Rectangle(centerX - ProxyHintWindowsSize2, size.Y - Proxy.HintWindowsSize - BorderMargin, Proxy.HintWindowsSize, Proxy.HintWindowsSize) + offset;
_rLeft = new Rectangle(BorderMargin, centerY - ProxyHintWindowsSize2, Proxy.HintWindowsSize, Proxy.HintWindowsSize) + offset;
_rRight = new Rectangle(size.X - Proxy.HintWindowsSize - BorderMargin, centerY - ProxyHintWindowsSize2, Proxy.HintWindowsSize, Proxy.HintWindowsSize) + offset;
_rCenter = new Rectangle(centerX - ProxyHintWindowsSize2, centerY - ProxyHintWindowsSize2, Proxy.HintWindowsSize, Proxy.HintWindowsSize) + offset;
var borderMargin = 4.0f;
var hintWindowsSize = Proxy.HintWindowsSize * Platform.DpiScale;
var hintWindowsSize2 = hintWindowsSize * 0.5f;
var centerX = size.X * 0.5f;
var centerY = size.Y * 0.5f;
_rUpper = new Rectangle(centerX - hintWindowsSize2, borderMargin, hintWindowsSize, hintWindowsSize) + offset;
_rBottom = new Rectangle(centerX - hintWindowsSize2, size.Y - hintWindowsSize - borderMargin, hintWindowsSize, hintWindowsSize) + offset;
_rLeft = new Rectangle(borderMargin, centerY - hintWindowsSize2, hintWindowsSize, hintWindowsSize) + offset;
_rRight = new Rectangle(size.X - hintWindowsSize - borderMargin, centerY - hintWindowsSize2, hintWindowsSize, hintWindowsSize) + offset;
_rCenter = new Rectangle(centerX - hintWindowsSize2, centerY - hintWindowsSize2, hintWindowsSize, hintWindowsSize) + offset;
// Hit test
DockState toSet = DockState.Float;
@@ -428,7 +430,6 @@ namespace FlaxEditor.GUI.Docking
{
if (Window == null)
{
// Create proxy window
var settings = CreateWindowSettings.Default;
settings.Title = "DockHint.Window";
settings.Size = initSize;
@@ -440,12 +441,10 @@ namespace FlaxEditor.GUI.Docking
settings.IsRegularWindow = false;
settings.SupportsTransparency = true;
settings.ShowInTaskbar = false;
settings.ShowAfterFirstPaint = true;
settings.ShowAfterFirstPaint = false;
settings.IsTopmost = true;
Window = Platform.CreateWindow(ref settings);
// Set opacity and background color
Window.Opacity = 0.6f;
Window.GUI.BackgroundColor = Style.Current.DragWindow;
}
@@ -465,7 +464,7 @@ namespace FlaxEditor.GUI.Docking
var settings = CreateWindowSettings.Default;
settings.Title = name;
settings.Size = new Float2(HintWindowsSize);
settings.Size = new Float2(HintWindowsSize * Platform.DpiScale);
settings.AllowInput = false;
settings.AllowMaximize = false;
settings.AllowMinimize = false;
@@ -479,7 +478,6 @@ namespace FlaxEditor.GUI.Docking
settings.ShowAfterFirstPaint = false;
win = Platform.CreateWindow(ref settings);
win.Opacity = 0.6f;
win.GUI.BackgroundColor = Style.Current.DragWindow;
}

View File

@@ -776,12 +776,21 @@ namespace FlaxEditor.GUI.Tree
// Check if mouse hits arrow
if (_mouseOverArrow && HasAnyVisibleChild)
{
// Toggle open state
if (ParentTree.Root.GetKey(KeyboardKeys.Alt))
{
if (_opened)
CollapseAll();
else
ExpandAll();
}
else
{
if (_opened)
Collapse();
else
Expand();
}
}
// Check if mouse hits bar
if (button == MouseButton.Right && TestHeaderHit(ref location))

View File

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

View File

@@ -237,7 +237,11 @@ namespace FlaxEditor.Modules
/// </summary>
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>

View File

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

View File

@@ -26,45 +26,108 @@ namespace FlaxEditor.Options
public float MouseWheelSensitivity { get; set; } = 1.0f;
/// <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>
[DefaultValue(1.0f), Limit(0.01f, 100.0f)]
[EditorDisplay("Defaults"), EditorOrder(110), Tooltip("The default movement speed for the viewport camera (must match the dropdown menu values in the viewport).")]
public float DefaultMovementSpeed { get; set; } = 1.0f;
[DefaultValue(64), Limit(1, 128)]
[EditorDisplay("Camera"), EditorOrder(110), Tooltip("The total amount of steps the camera needs to go from minimum to maximum speed.")]
public int TotalCameraSpeedSteps { get; set; } = 64;
/// <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>
/// Gets or sets the default near clipping plane distance for the viewport camera.
/// </summary>
[DefaultValue(10.0f), Limit(0.001f, 1000.0f)]
[EditorDisplay("Defaults"), EditorOrder(120), Tooltip("The default near clipping plane distance for the viewport camera.")]
public float DefaultNearPlane { get; set; } = 10.0f;
[EditorDisplay("Defaults"), EditorOrder(140), Tooltip("The default near clipping plane distance for the viewport camera.")]
public float NearPlane { get; set; } = 10.0f;
/// <summary>
/// Gets or sets the default far clipping plane distance for the viewport camera.
/// </summary>
[DefaultValue(40000.0f), Limit(10.0f)]
[EditorDisplay("Defaults"), EditorOrder(130), Tooltip("The default far clipping plane distance for the viewport camera.")]
public float DefaultFarPlane { get; set; } = 40000.0f;
[EditorDisplay("Defaults"), EditorOrder(150), Tooltip("The default far clipping plane distance for the viewport camera.")]
public float FarPlane { get; set; } = 40000.0f;
/// <summary>
/// Gets or sets the default field of view angle (in degrees) for the viewport camera.
/// </summary>
[DefaultValue(60.0f), Limit(35.0f, 160.0f, 0.1f)]
[EditorDisplay("Defaults", "Default Field Of View"), EditorOrder(140), Tooltip("The default field of view angle (in degrees) for the viewport camera.")]
public float DefaultFieldOfView { get; set; } = 60.0f;
[EditorDisplay("Defaults"), EditorOrder(160), Tooltip("The default field of view angle (in degrees) for the viewport camera.")]
public float FieldOfView { get; set; } = 60.0f;
/// <summary>
/// Gets or sets if the panning direction is inverted for the viewport camera.
/// Gets or sets the default camera orthographic mode.
/// </summary>
[DefaultValue(false)]
[EditorDisplay("Defaults"), EditorOrder(150), Tooltip("Invert the panning direction for the viewport camera.")]
public bool DefaultInvertPanning { get; set; } = false;
[EditorDisplay("Defaults"), EditorOrder(170), Tooltip("The default camera orthographic mode.")]
public bool UseOrthographicProjection { get; set; } = false;
/// <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>
[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;
}
}

View File

@@ -154,7 +154,8 @@ bool ProjectInfo::LoadProject(const String& projectPath)
Version = ::Version(
JsonTools::GetInt(version, "Major", 0),
JsonTools::GetInt(version, "Minor", 0),
JsonTools::GetInt(version, "Build", 0));
JsonTools::GetInt(version, "Build", -1),
JsonTools::GetInt(version, "Revision", -1));
}
}
if (Version.Revision() == 0)

View File

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

View File

@@ -66,7 +66,8 @@ namespace FlaxEditor.SceneGraph.GUI
_orderInParent = actor.OrderInParent;
Visible = (actor.HideFlags & HideFlags.HideInHierarchy) == 0;
var id = actor.ID;
// Pick the correct id when inside a prefab window.
var id = actor.HasPrefabLink && actor.Scene == null ? actor.PrefabObjectID : actor.ID;
if (Editor.Instance.ProjectCache.IsExpandedActor(ref id))
{
Expand(true);
@@ -171,7 +172,8 @@ namespace FlaxEditor.SceneGraph.GUI
// Restore cached state on query filter clear
if (noFilter && actor != null)
{
var id = actor.ID;
// Pick the correct id when inside a prefab window.
var id = actor.HasPrefabLink && actor.Scene.Scene == null ? actor.PrefabObjectID : actor.ID;
isExpanded = Editor.Instance.ProjectCache.IsExpandedActor(ref id);
}
@@ -301,10 +303,12 @@ namespace FlaxEditor.SceneGraph.GUI
protected override void OnExpandedChanged()
{
base.OnExpandedChanged();
var actor = Actor;
if (!IsLayoutLocked && Actor)
if (!IsLayoutLocked && actor)
{
var id = Actor.ID;
// Pick the correct id when inside a prefab window.
var id = actor.HasPrefabLink && actor.Scene == null ? actor.PrefabObjectID : actor.ID;
Editor.Instance.ProjectCache.SetExpandedActor(ref id, IsExpanded);
}
}

View File

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

View File

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

View File

@@ -128,12 +128,26 @@ namespace FlaxEditor.Viewport
public const int FpsCameraFilteringFrames = 3;
/// <summary>
/// The speed widget button.
/// The camera settings widget.
/// </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 _movementSpeed;
private float _minMovementSpeed;
private float _maxMovementSpeed;
private float _mouseAccelerationScale;
private bool _useMouseFiltering;
private bool _useMouseAcceleration;
@@ -174,11 +188,17 @@ namespace FlaxEditor.Viewport
private float _fieldOfView;
private float _nearPlane;
private float _farPlane;
private float _orthoSize = 1.0f;
private bool _isOrtho = false;
private float _wheelMovementChangeDeltaSum = 0;
private float _orthoSize;
private bool _isOrtho;
private bool _useCameraEasing;
private float _cameraEasingDegree;
private float _panningSpeed;
private bool _relativePanning;
private bool _invertPanning;
private int _speedStep;
private int _maxSpeedSteps;
/// <summary>
/// Speed of the mouse.
/// </summary>
@@ -189,6 +209,25 @@ namespace FlaxEditor.Viewport
/// </summary>
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>
/// Gets or sets the camera movement speed.
/// </summary>
@@ -197,17 +236,38 @@ namespace FlaxEditor.Viewport
get => _movementSpeed;
set
{
for (int i = 0; i < EditorViewportCameraSpeedValues.Length; i++)
_movementSpeed = value;
if (_cameraButton != null)
_cameraButton.Text = string.Format(MovementSpeedTextFormat, _movementSpeed);
}
}
/// <summary>
/// Gets or sets the minimum camera movement speed.
/// </summary>
public float MinMovementSpeed
{
if (Math.Abs(value - EditorViewportCameraSpeedValues[i]) < 0.001f)
get => _minMovementSpeed;
set => _minMovementSpeed = value;
}
/// <summary>
/// Gets or sets the maximum camera movement speed.
/// </summary>
public float MaxMovementSpeed
{
_movementSpeed = EditorViewportCameraSpeedValues[i];
if (_speedWidget != null)
_speedWidget.Text = _movementSpeed.ToString();
break;
}
}
get => _maxMovementSpeed;
set => _maxMovementSpeed = value;
}
/// <summary>
/// Gets or sets the camera easing mode.
/// </summary>
public bool UseCameraEasing
{
get => _useCameraEasing;
set => _useCameraEasing = value;
}
/// <summary>
@@ -396,6 +456,15 @@ namespace FlaxEditor.Viewport
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>
/// Gets or sets if the panning direction is inverted.
/// </summary>
@@ -405,6 +474,15 @@ namespace FlaxEditor.Viewport
set => _invertPanning = value;
}
/// <summary>
/// Gets or sets the camera panning speed.
/// </summary>
public float PanningSpeed
{
get => _panningSpeed;
set => _panningSpeed = value;
}
/// <summary>
/// The input actions collection to processed during user input.
/// </summary>
@@ -419,6 +497,8 @@ namespace FlaxEditor.Viewport
public EditorViewport(SceneRenderTask task, ViewportCamera camera, bool useWidgets)
: base(task)
{
_editor = Editor.Instance;
_mouseAccelerationScale = 0.1f;
_useMouseFiltering = false;
_useMouseAcceleration = false;
@@ -431,43 +511,299 @@ namespace FlaxEditor.Viewport
// Setup options
{
var options = Editor.Instance.Options.Options;
_movementSpeed = options.Viewport.DefaultMovementSpeed;
_nearPlane = options.Viewport.DefaultNearPlane;
_farPlane = options.Viewport.DefaultFarPlane;
_fieldOfView = options.Viewport.DefaultFieldOfView;
_invertPanning = options.Viewport.DefaultInvertPanning;
Editor.Instance.Options.OptionsChanged += OnEditorOptionsChanged;
OnEditorOptionsChanged(options);
SetupViewportOptions();
}
// Initialize camera values from cache
if (_editor.ProjectCache.TryGetCustomData("CameraMovementSpeedValue", out var cachedState))
MovementSpeed = float.Parse(cachedState);
if (_editor.ProjectCache.TryGetCustomData("CameraMinMovementSpeedValue", out cachedState))
_minMovementSpeed = float.Parse(cachedState);
if (_editor.ProjectCache.TryGetCustomData("CameraMaxMovementSpeedValue", out cachedState))
_maxMovementSpeed = float.Parse(cachedState);
if (_editor.ProjectCache.TryGetCustomData("UseCameraEasingState", out cachedState))
_useCameraEasing = bool.Parse(cachedState);
if (_editor.ProjectCache.TryGetCustomData("CameraPanningSpeedValue", out cachedState))
_panningSpeed = float.Parse(cachedState);
if (_editor.ProjectCache.TryGetCustomData("CameraInvertPanningState", out cachedState))
_invertPanning = bool.Parse(cachedState);
if (_editor.ProjectCache.TryGetCustomData("CameraRelativePanningState", out cachedState))
_relativePanning = bool.Parse(cachedState);
if (_editor.ProjectCache.TryGetCustomData("CameraOrthographicState", out cachedState))
_isOrtho = bool.Parse(cachedState);
if (_editor.ProjectCache.TryGetCustomData("CameraOrthographicSizeValue", out cachedState))
_orthoSize = float.Parse(cachedState);
if (_editor.ProjectCache.TryGetCustomData("CameraFieldOfViewValue", out cachedState))
_fieldOfView = float.Parse(cachedState);
if (_editor.ProjectCache.TryGetCustomData("CameraNearPlaneValue", out cachedState))
_nearPlane = float.Parse(cachedState);
if (_editor.ProjectCache.TryGetCustomData("CameraFarPlaneValue", out cachedState))
_farPlane = float.Parse(cachedState);
OnCameraMovementProgressChanged();
if (useWidgets)
{
var largestText = "Invert Panning";
#region Camera settings widget
var largestText = "Relative Panning";
var textSize = Style.Current.FontMedium.MeasureText(largestText);
var xLocationForExtras = textSize.X + 5;
// Camera speed widget
var camSpeed = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
var camSpeedCM = new ContextMenu();
var camSpeedButton = new ViewportWidgetButton(_movementSpeed.ToString(), Editor.Instance.Icons.CamSpeed32, camSpeedCM)
var cameraSpeedTextWidth = Style.Current.FontMedium.MeasureText("0.00").X;
// Camera Settings Widget
_cameraWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
// Camera Settings Menu
var cameraCM = new ContextMenu();
_cameraButton = new ViewportWidgetButton(string.Format(MovementSpeedTextFormat, _movementSpeed), Editor.Instance.Icons.Camera64, cameraCM, false, cameraSpeedTextWidth)
{
Tag = this,
TooltipText = "Camera speed scale"
TooltipText = "Camera Settings",
Parent = _cameraWidget
};
_speedWidget = camSpeedButton;
for (int i = 0; i < EditorViewportCameraSpeedValues.Length; i++)
{
var v = EditorViewportCameraSpeedValues[i];
var button = camSpeedCM.AddButton(v.ToString());
button.Tag = v;
}
camSpeedCM.ButtonClicked += button => MovementSpeed = (float)button.Tag;
camSpeedCM.VisibleChanged += WidgetCamSpeedShowHide;
camSpeedButton.Parent = camSpeed;
camSpeed.Parent = this;
_cameraWidget.Parent = this;
// Orthographic/Perspective Mode Widget
_orthographicModeButton = new ViewportWidgetButton(string.Empty, Editor.Instance.Icons.CamSpeed32, null, true)
{
Checked = !_isOrtho,
TooltipText = "Toggle Orthographic/Perspective Mode",
Parent = _cameraWidget
};
_orthographicModeButton.Toggled += OnOrthographicModeToggled;
// Camera Speed
var camSpeedButton = cameraCM.AddButton("Camera Speed");
camSpeedButton.CloseMenuOnClick = false;
var camSpeedValue = new FloatValueBox(_movementSpeed, xLocationForExtras, 2, 70.0f, _minMovementSpeed, _maxMovementSpeed, 0.5f)
{
Parent = camSpeedButton
};
camSpeedValue.ValueChanged += () => OnMovementSpeedChanged(camSpeedValue);
cameraCM.VisibleChanged += control => camSpeedValue.Value = _movementSpeed;
// Minimum & Maximum Camera Speed
var minCamSpeedButton = cameraCM.AddButton("Min Cam Speed");
minCamSpeedButton.CloseMenuOnClick = false;
var minCamSpeedValue = new FloatValueBox(_minMovementSpeed, xLocationForExtras, 2, 70.0f, 0.05f, _maxMovementSpeed, 0.5f)
{
Parent = minCamSpeedButton
};
var maxCamSpeedButton = cameraCM.AddButton("Max Cam Speed");
maxCamSpeedButton.CloseMenuOnClick = false;
var maxCamSpeedValue = new FloatValueBox(_maxMovementSpeed, xLocationForExtras, 2, 70.0f, _minMovementSpeed, 1000.0f, 0.5f)
{
Parent = maxCamSpeedButton
};
minCamSpeedValue.ValueChanged += () =>
{
OnMinMovementSpeedChanged(minCamSpeedValue);
maxCamSpeedValue.MinValue = minCamSpeedValue.Value;
if (Math.Abs(camSpeedValue.MinValue - minCamSpeedValue.Value) > Mathf.Epsilon)
camSpeedValue.MinValue = minCamSpeedValue.Value;
};
cameraCM.VisibleChanged += control => minCamSpeedValue.Value = _minMovementSpeed;
maxCamSpeedValue.ValueChanged += () =>
{
OnMaxMovementSpeedChanged(maxCamSpeedValue);
minCamSpeedValue.MaxValue = maxCamSpeedValue.Value;
if (Math.Abs(camSpeedValue.MaxValue - maxCamSpeedValue.Value) > Mathf.Epsilon)
camSpeedValue.MaxValue = maxCamSpeedValue.Value;
};
cameraCM.VisibleChanged += control => maxCamSpeedValue.Value = _maxMovementSpeed;
// Camera Easing
{
var useCameraEasing = cameraCM.AddButton("Camera Easing");
useCameraEasing.CloseMenuOnClick = false;
var useCameraEasingValue = new CheckBox(xLocationForExtras, 2, _useCameraEasing)
{
Parent = useCameraEasing
};
useCameraEasingValue.StateChanged += OnCameraEasingToggled;
cameraCM.VisibleChanged += control => useCameraEasingValue.Checked = _useCameraEasing;
}
// Panning Speed
{
var panningSpeed = cameraCM.AddButton("Panning Speed");
panningSpeed.CloseMenuOnClick = false;
var panningSpeedValue = new FloatValueBox(_panningSpeed, xLocationForExtras, 2, 70.0f, 0.01f, 128.0f, 0.1f)
{
Parent = panningSpeed
};
panningSpeedValue.ValueChanged += () => OnPanningSpeedChanged(panningSpeedValue);
cameraCM.VisibleChanged += control =>
{
panningSpeed.Visible = !_relativePanning;
panningSpeedValue.Value = _panningSpeed;
};
}
// Relative Panning
{
var relativePanning = cameraCM.AddButton("Relative Panning");
relativePanning.CloseMenuOnClick = false;
var relativePanningValue = new CheckBox(xLocationForExtras, 2, _relativePanning)
{
Parent = relativePanning
};
relativePanningValue.StateChanged += checkBox =>
{
if (checkBox.Checked != _relativePanning)
{
OnRelativePanningToggled(checkBox);
cameraCM.Hide();
}
};
cameraCM.VisibleChanged += control => relativePanningValue.Checked = _relativePanning;
}
// Invert Panning
{
var invertPanning = cameraCM.AddButton("Invert Panning");
invertPanning.CloseMenuOnClick = false;
var invertPanningValue = new CheckBox(xLocationForExtras, 2, _invertPanning)
{
Parent = invertPanning
};
invertPanningValue.StateChanged += OnInvertPanningToggled;
cameraCM.VisibleChanged += control => invertPanningValue.Checked = _invertPanning;
}
cameraCM.AddSeparator();
// Camera Viewpoints
{
var cameraView = cameraCM.AddChildMenu("Viewpoints").ContextMenu;
for (int i = 0; i < EditorViewportCameraViewpointValues.Length; i++)
{
var co = EditorViewportCameraViewpointValues[i];
var button = cameraView.AddButton(co.Name);
button.Tag = co.Orientation;
}
cameraView.ButtonClicked += OnViewpointChanged;
}
// Orthographic Mode
{
var ortho = cameraCM.AddButton("Orthographic");
ortho.CloseMenuOnClick = false;
var orthoValue = new CheckBox(xLocationForExtras, 2, _isOrtho)
{
Parent = ortho
};
orthoValue.StateChanged += checkBox =>
{
if (checkBox.Checked != _isOrtho)
{
OnOrthographicModeToggled(checkBox);
cameraCM.Hide();
}
};
cameraCM.VisibleChanged += control => orthoValue.Checked = _isOrtho;
}
// Field of View
{
var fov = cameraCM.AddButton("Field Of View");
fov.CloseMenuOnClick = false;
var fovValue = new FloatValueBox(_fieldOfView, xLocationForExtras, 2, 70.0f, 35.0f, 160.0f, 0.1f)
{
Parent = fov
};
fovValue.ValueChanged += () => OnFieldOfViewChanged(fovValue);
cameraCM.VisibleChanged += control =>
{
fov.Visible = !_isOrtho;
fovValue.Value = _fieldOfView;
};
}
// Orthographic Scale
{
var orthoSize = cameraCM.AddButton("Ortho Scale");
orthoSize.CloseMenuOnClick = false;
var orthoSizeValue = new FloatValueBox(_orthoSize, xLocationForExtras, 2, 70.0f, 0.001f, 100000.0f, 0.01f)
{
Parent = orthoSize
};
orthoSizeValue.ValueChanged += () => OnOrthographicSizeChanged(orthoSizeValue);
cameraCM.VisibleChanged += control =>
{
orthoSize.Visible = _isOrtho;
orthoSizeValue.Value = _orthoSize;
};
}
// Near Plane
{
var nearPlane = cameraCM.AddButton("Near Plane");
nearPlane.CloseMenuOnClick = false;
var nearPlaneValue = new FloatValueBox(_nearPlane, xLocationForExtras, 2, 70.0f, 0.001f, 1000.0f)
{
Parent = nearPlane
};
nearPlaneValue.ValueChanged += () => OnNearPlaneChanged(nearPlaneValue);
cameraCM.VisibleChanged += control => nearPlaneValue.Value = _nearPlane;
}
// Far Plane
{
var farPlane = cameraCM.AddButton("Far Plane");
farPlane.CloseMenuOnClick = false;
var farPlaneValue = new FloatValueBox(_farPlane, xLocationForExtras, 2, 70.0f, 10.0f)
{
Parent = farPlane
};
farPlaneValue.ValueChanged += () => OnFarPlaneChanged(farPlaneValue);
cameraCM.VisibleChanged += control => farPlaneValue.Value = _farPlane;
}
cameraCM.AddSeparator();
// Reset Button
{
var reset = cameraCM.AddButton("Reset to default");
reset.ButtonClicked += button =>
{
SetupViewportOptions();
// if the context menu is opened without triggering the value changes beforehand,
// the movement speed will not be correctly reset to its default value in certain cases
// therefore, a UI update needs to be triggered here
minCamSpeedValue.Value = _minMovementSpeed;
camSpeedValue.Value = _movementSpeed;
maxCamSpeedValue.Value = _maxMovementSpeed;
};
}
#endregion Camera settings widget
#region View mode widget
largestText = "Brightness";
textSize = Style.Current.FontMedium.MeasureText(largestText);
xLocationForExtras = textSize.X + 5;
// View mode widget
var viewMode = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperLeft);
ViewWidgetButtonMenu = new ContextMenu();
var viewModeButton = new ViewportWidgetButton("View", SpriteHandle.Invalid, ViewWidgetButtonMenu)
@@ -484,8 +820,8 @@ namespace FlaxEditor.Viewport
// Show FPS
{
InitFpsCounter();
_showFpsButon = ViewWidgetShowMenu.AddButton("FPS Counter", () => ShowFpsCounter = !ShowFpsCounter);
_showFpsButon.CloseMenuOnClick = false;
_showFpsButton = ViewWidgetShowMenu.AddButton("FPS Counter", () => ShowFpsCounter = !ShowFpsCounter);
_showFpsButton.CloseMenuOnClick = false;
}
}
@@ -610,104 +946,6 @@ namespace FlaxEditor.Viewport
ViewWidgetButtonMenu.AddSeparator();
// Orthographic
{
var ortho = ViewWidgetButtonMenu.AddButton("Orthographic");
ortho.CloseMenuOnClick = false;
var orthoValue = new CheckBox(xLocationForExtras, 2, _isOrtho)
{
Parent = ortho
};
orthoValue.StateChanged += checkBox =>
{
if (checkBox.Checked != _isOrtho)
{
_isOrtho = checkBox.Checked;
ViewWidgetButtonMenu.Hide();
if (_isOrtho)
{
var orient = ViewOrientation;
OrientViewport(ref orient);
}
}
};
ViewWidgetButtonMenu.VisibleChanged += control => orthoValue.Checked = _isOrtho;
}
// Camera Viewpoints
{
var cameraView = ViewWidgetButtonMenu.AddChildMenu("Viewpoints").ContextMenu;
for (int i = 0; i < EditorViewportCameraViewpointValues.Length; i++)
{
var co = EditorViewportCameraViewpointValues[i];
var button = cameraView.AddButton(co.Name);
button.Tag = co.Orientation;
}
cameraView.ButtonClicked += button =>
{
var orient = Quaternion.Euler((Float3)button.Tag);
OrientViewport(ref orient);
};
}
// Field of View
{
var fov = ViewWidgetButtonMenu.AddButton("Field Of View");
fov.CloseMenuOnClick = false;
var fovValue = new FloatValueBox(1, xLocationForExtras, 2, 70.0f, 35.0f, 160.0f, 0.1f)
{
Parent = fov
};
fovValue.ValueChanged += () => _fieldOfView = fovValue.Value;
ViewWidgetButtonMenu.VisibleChanged += control =>
{
fov.Visible = !_isOrtho;
fovValue.Value = _fieldOfView;
};
}
// Ortho Scale
{
var orthoSize = ViewWidgetButtonMenu.AddButton("Ortho Scale");
orthoSize.CloseMenuOnClick = false;
var orthoSizeValue = new FloatValueBox(_orthoSize, xLocationForExtras, 2, 70.0f, 0.001f, 100000.0f, 0.01f)
{
Parent = orthoSize
};
orthoSizeValue.ValueChanged += () => _orthoSize = orthoSizeValue.Value;
ViewWidgetButtonMenu.VisibleChanged += control =>
{
orthoSize.Visible = _isOrtho;
orthoSizeValue.Value = _orthoSize;
};
}
// Near Plane
{
var nearPlane = ViewWidgetButtonMenu.AddButton("Near Plane");
nearPlane.CloseMenuOnClick = false;
var nearPlaneValue = new FloatValueBox(2.0f, xLocationForExtras, 2, 70.0f, 0.001f, 1000.0f)
{
Parent = nearPlane
};
nearPlaneValue.ValueChanged += () => _nearPlane = nearPlaneValue.Value;
ViewWidgetButtonMenu.VisibleChanged += control => nearPlaneValue.Value = _nearPlane;
}
// Far Plane
{
var farPlane = ViewWidgetButtonMenu.AddButton("Far Plane");
farPlane.CloseMenuOnClick = false;
var farPlaneValue = new FloatValueBox(1000, xLocationForExtras, 2, 70.0f, 10.0f)
{
Parent = farPlane
};
farPlaneValue.ValueChanged += () => _farPlane = farPlaneValue.Value;
ViewWidgetButtonMenu.VisibleChanged += control => farPlaneValue.Value = _farPlane;
}
// Brightness
{
var brightness = ViewWidgetButtonMenu.AddButton("Brightness");
@@ -732,24 +970,7 @@ namespace FlaxEditor.Viewport
ViewWidgetButtonMenu.VisibleChanged += control => resolutionValue.Value = ResolutionScale;
}
// Invert Panning
{
var invert = ViewWidgetButtonMenu.AddButton("Invert Panning");
invert.CloseMenuOnClick = false;
var invertValue = new CheckBox(xLocationForExtras, 2, _invertPanning)
{
Parent = invert
};
invertValue.StateChanged += checkBox =>
{
if (checkBox.Checked != _invertPanning)
{
_invertPanning = checkBox.Checked;
}
};
ViewWidgetButtonMenu.VisibleChanged += control => invertValue.Checked = _invertPanning;
}
#endregion View mode widget
}
InputActions.Add(options => options.ViewpointTop, () => OrientViewport(Quaternion.Euler(EditorViewportCameraViewpointValues.First(vp => vp.Name == "Top").Orientation)));
@@ -766,6 +987,135 @@ namespace FlaxEditor.Viewport
task.Begin += OnRenderBegin;
}
/// <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>
/// Gets a value indicating whether this viewport is using mouse currently (eg. user moving objects).
/// </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>
/// Increases or decreases the camera movement speed.
/// </summary>
/// <param name="step">The stepping direction for speed adjustment.</param>
protected void AdjustCameraMoveSpeed(int step)
{
int camValueIndex = -1;
for (int i = 0; i < EditorViewportCameraSpeedValues.Length; i++)
{
if (Mathf.NearEqual(EditorViewportCameraSpeedValues[i], _movementSpeed))
{
camValueIndex = i;
break;
}
}
if (camValueIndex == -1)
return;
_speedStep = Mathf.Clamp(_speedStep + step, 0, _maxSpeedSteps);
if (step > 0)
MovementSpeed = EditorViewportCameraSpeedValues[Mathf.Min(camValueIndex + 1, EditorViewportCameraSpeedValues.Length - 1)];
else if (step < 0)
MovementSpeed = EditorViewportCameraSpeedValues[Mathf.Max(camValueIndex - 1, 0)];
// calculate new linear/eased progress
var progress = _useCameraEasing
? Mathf.Pow((float)_speedStep / _maxSpeedSteps, _cameraEasingDegree)
: (float)_speedStep / _maxSpeedSteps;
var speed = Mathf.Lerp(_minMovementSpeed, _maxMovementSpeed, progress);
MovementSpeed = (float)Math.Round(speed, 3);
_editor.ProjectCache.SetCustomData("CameraMovementSpeedValue", _movementSpeed.ToString());
}
private void OnEditorOptionsChanged(EditorOptions options)
{
_mouseSensitivity = options.Viewport.MouseSensitivity;
_maxSpeedSteps = options.Viewport.TotalCameraSpeedSteps;
_cameraEasingDegree = options.Viewport.CameraEasingDegree;
OnCameraMovementProgressChanged();
}
private void OnRenderBegin(RenderTask task, GPUContext context)
@@ -863,7 +1239,7 @@ namespace FlaxEditor.Viewport
}
private FpsCounter _fpsCounter;
private ContextMenuButton _showFpsButon;
private ContextMenuButton _showFpsButton;
/// <summary>
/// Gets or sets a value indicating whether show or hide FPS counter.
@@ -875,7 +1251,7 @@ namespace FlaxEditor.Viewport
{
_fpsCounter.Visible = value;
_fpsCounter.Enabled = value;
_showFpsButon.Icon = value ? Style.Current.CheckBoxTick : SpriteHandle.Invalid;
_showFpsButton.Icon = value ? Style.Current.CheckBoxTick : SpriteHandle.Invalid;
}
}
@@ -1016,8 +1392,6 @@ namespace FlaxEditor.Viewport
/// <param name="win">The parent window.</param>
protected virtual void OnControlMouseBegin(Window win)
{
_wheelMovementChangeDeltaSum = 0;
// Hide cursor and start tracking mouse movement
win.StartTrackingMouse(false);
win.Cursor = CursorType.Hidden;
@@ -1113,8 +1487,8 @@ namespace FlaxEditor.Viewport
_camera.Update(deltaTime);
useMovementSpeed = _camera.UseMovementSpeed;
if (_speedWidget != null)
_speedWidget.Parent.Visible = useMovementSpeed;
if (_cameraButton != null)
_cameraButton.Parent.Visible = useMovementSpeed;
}
// Get parent window
@@ -1217,18 +1591,8 @@ namespace FlaxEditor.Viewport
rmbWheel = useMovementSpeed && (_input.IsMouseRightDown || _isVirtualMouseRightDown) && wheelInUse;
if (rmbWheel)
{
const float step = 4.0f;
_wheelMovementChangeDeltaSum += _input.MouseWheelDelta * options.Viewport.MouseWheelSensitivity;
if (_wheelMovementChangeDeltaSum >= step)
{
_wheelMovementChangeDeltaSum -= step;
AdjustCameraMoveSpeed(1);
}
else if (_wheelMovementChangeDeltaSum <= -step)
{
_wheelMovementChangeDeltaSum += step;
AdjustCameraMoveSpeed(-1);
}
var step = _input.MouseWheelDelta * options.Viewport.MouseWheelSensitivity;
AdjustCameraMoveSpeed(step > 0.0f ? 1 : -1);
}
}
@@ -1497,22 +1861,6 @@ namespace FlaxEditor.Viewport
new CameraViewpoint("Bottom", new Float3(-90, 0, 0))
};
private readonly float[] EditorViewportCameraSpeedValues =
{
0.05f,
0.1f,
0.25f,
0.5f,
1.0f,
2.0f,
4.0f,
6.0f,
8.0f,
16.0f,
32.0f,
64.0f,
};
private struct ViewModeOptions
{
public readonly string Name;
@@ -1568,24 +1916,6 @@ namespace FlaxEditor.Viewport
new ViewModeOptions(ViewMode.GlobalIllumination, "Global Illumination"),
};
private void WidgetCamSpeedShowHide(Control cm)
{
if (cm.Visible == false)
return;
var ccm = (ContextMenu)cm;
foreach (var e in ccm.Items)
{
if (e is ContextMenuButton b)
{
var v = (float)b.Tag;
b.Icon = Mathf.Abs(MovementSpeed - v) < 0.001f
? Style.Current.CheckBoxTick
: SpriteHandle.Invalid;
}
}
}
private void WidgetViewModeShowHideClicked(ContextMenuButton button)
{
if (button.Tag is ViewMode v)

View File

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

View File

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

View File

@@ -369,7 +369,7 @@ namespace FlaxEditor.Windows
}
var pluginPath = Path.Combine(Globals.ProjectFolder, "Source", nameTextBox.Text);
if (Directory.Exists(pluginPath))
if (!IsValidModuleName(nameTextBox.Text) || Directory.Exists(pluginPath))
{
nameTextBox.BorderColor = Color.Red;
nameTextBox.BorderSelectedColor = Color.Red;
@@ -429,6 +429,12 @@ namespace FlaxEditor.Windows
submitButton.Clicked += () =>
{
// TODO: Check all modules in project including plugins
if (!IsValidModuleName(nameTextBox.Text))
{
Editor.LogWarning("Invalid module name. Module names cannot contain spaces, start with a number or contain non-alphanumeric characters.");
return;
}
if (Directory.Exists(Path.Combine(Globals.ProjectFolder, "Source", nameTextBox.Text)))
{
Editor.LogWarning("Cannot create module due to name conflict.");
@@ -460,5 +466,16 @@ namespace FlaxEditor.Windows
button.ParentContextMenu.Hide();
};
}
private static bool IsValidModuleName(string text)
{
if (text.Contains(' '))
return false;
if (char.IsDigit(text[0]))
return false;
if (text.Any(c => !char.IsLetterOrDigit(c) && c != '_'))
return false;
return true;
}
}
}

View File

@@ -155,30 +155,43 @@ namespace FlaxEditor.Windows
public virtual void OnNotAvailableLayout(LayoutElementsContainer layout)
{
layout.Label("Missing platform data tools for the target platform.", TextAlignment.Center);
string text = "Missing platform data tools for the target platform.";
if (FlaxEditor.Editor.IsOfficialBuild())
{
switch (BuildPlatform)
{
#if PLATFORM_WINDOWS
case BuildPlatform.Windows32:
case BuildPlatform.Windows64:
case BuildPlatform.UWPx86:
case BuildPlatform.UWPx64:
case BuildPlatform.LinuxX64:
case BuildPlatform.AndroidARM64:
layout.Label("Use Flax Launcher and download the required package.", TextAlignment.Center);
text += "\nUse Flax Launcher and download the required package.";
break;
#endif
default:
layout.Label("Engine source is required to target this platform.", TextAlignment.Center);
text += "\nEngine source is required to target this platform.";
break;
}
}
else
{
var label = layout.Label("To target this platform separate engine source package is required.\nTo get access please contact via https://flaxengine.com/contact", TextAlignment.Center);
label.Label.AutoHeight = true;
text += "\nTo target this platform separate engine source package is required.";
switch (BuildPlatform)
{
case BuildPlatform.XboxOne:
case BuildPlatform.XboxScarlett:
case BuildPlatform.PS4:
case BuildPlatform.PS5:
case BuildPlatform.Switch:
text += "\nTo get access please contact via https://flaxengine.com/contact";
break;
}
}
var label = layout.Label(text, TextAlignment.Center);
label.Label.AutoHeight = true;
}
public virtual void Build()
{

View File

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

View File

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

View File

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

View File

@@ -7,34 +7,14 @@ using System.Globalization;
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 />
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
string[] v = str.Split(',');
string[] v = GetParts(str);
return new Double2(double.Parse(v[0], culture), double.Parse(v[1], culture));
}
return base.ConvertFrom(context, culture, value);

View File

@@ -7,34 +7,14 @@ using System.Globalization;
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 />
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
string[] v = str.Split(',');
string[] v = GetParts(str);
return new Double3(double.Parse(v[0], culture), double.Parse(v[1], culture), double.Parse(v[2], culture));
}
return base.ConvertFrom(context, culture, value);

View File

@@ -7,34 +7,14 @@ using System.Globalization;
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 />
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
string[] v = str.Split(',');
string[] v = GetParts(str);
return new Double4(double.Parse(v[0], culture), double.Parse(v[1], culture), double.Parse(v[2], culture), double.Parse(v[3], culture));
}
return base.ConvertFrom(context, culture, value);

View File

@@ -7,34 +7,14 @@ using System.Globalization;
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 />
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
string[] v = str.Split(',');
string[] v = GetParts(str);
return new Float2(float.Parse(v[0], culture), float.Parse(v[1], culture));
}
return base.ConvertFrom(context, culture, value);

View File

@@ -7,34 +7,14 @@ using System.Globalization;
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 />
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
string[] v = str.Split(',');
string[] v = GetParts(str);
return new Float3(float.Parse(v[0], culture), float.Parse(v[1], culture), float.Parse(v[2], culture));
}
return base.ConvertFrom(context, culture, value);

View File

@@ -7,15 +7,13 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters
{
internal class Float4Converter : TypeConverter
internal class VectorConverter : TypeConverter
{
/// <inheritdoc />
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
@@ -23,18 +21,32 @@ namespace FlaxEngine.TypeConverters
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
{
return false;
}
return base.CanConvertTo(context, destinationType);
}
internal static string[] GetParts(string str)
{
string[] v = str.Split(',');
if (v.Length == 1)
{
// When converting from ToString()
v = str.Split(' ');
for (int i = 0; i < v.Length; i++)
v[i] = v[i].Substring(v[i].IndexOf(':') + 1);
}
return v;
}
}
internal class Float4Converter : VectorConverter
{
/// <inheritdoc />
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
string[] v = str.Split(',');
string[] v = GetParts(str);
return new Float4(float.Parse(v[0], culture), float.Parse(v[1], culture), float.Parse(v[2], culture), float.Parse(v[3], culture));
}
return base.ConvertFrom(context, culture, value);

View File

@@ -7,34 +7,14 @@ using System.Globalization;
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 />
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
string[] v = str.Split(',');
string[] v = GetParts(str);
return new Int2(int.Parse(v[0], culture), int.Parse(v[1], culture));
}
return base.ConvertFrom(context, culture, value);

View File

@@ -7,34 +7,14 @@ using System.Globalization;
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 />
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
string[] v = str.Split(',');
string[] v = GetParts(str);
return new Int3(int.Parse(v[0], culture), int.Parse(v[1], culture), int.Parse(v[2], culture));
}
return base.ConvertFrom(context, culture, value);

View File

@@ -7,34 +7,14 @@ using System.Globalization;
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 />
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
string[] v = str.Split(',');
string[] v = GetParts(str);
return new Int4(int.Parse(v[0], culture), int.Parse(v[1], culture), int.Parse(v[2], culture), int.Parse(v[3], culture));
}
return base.ConvertFrom(context, culture, value);

View File

@@ -7,34 +7,14 @@ using System.Globalization;
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 />
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
string[] v = str.Split(',');
string[] v = GetParts(str);
return new Quaternion(float.Parse(v[0], culture), float.Parse(v[1], culture), float.Parse(v[2], culture), float.Parse(v[3], culture));
}
return base.ConvertFrom(context, culture, value);

View File

@@ -7,34 +7,14 @@ using System.Globalization;
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 />
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
string[] v = str.Split(',');
string[] v = GetParts(str);
return new Vector2(float.Parse(v[0], culture), float.Parse(v[1], culture));
}
return base.ConvertFrom(context, culture, value);

View File

@@ -7,34 +7,14 @@ using System.Globalization;
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 />
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
string[] v = str.Split(',');
string[] v = GetParts(str);
return new Vector3(float.Parse(v[0], culture), float.Parse(v[1], culture), float.Parse(v[2], culture));
}
return base.ConvertFrom(context, culture, value);

View File

@@ -7,34 +7,14 @@ using System.Globalization;
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 />
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
string[] v = str.Split(',');
string[] v = GetParts(str);
return new Vector4(float.Parse(v[0], culture), float.Parse(v[1], culture), float.Parse(v[2], culture), float.Parse(v[3], culture));
}
return base.ConvertFrom(context, culture, value);

View File

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

View File

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

View File

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

View File

@@ -51,10 +51,14 @@ namespace Utilities
int32 i = 0;
double dblSUnits = static_cast<double>(units);
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())
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

View File

@@ -1302,7 +1302,8 @@ namespace FlaxEngine.Interop
#if !USE_AOT
internal bool TryGetDelegate(out Invoker.MarshalAndInvokeDelegate outDeleg, out object outDelegInvoke)
{
if (invokeDelegate == null)
// Skip using in-built delegate for value types (eg. Transform) to properly handle instance value passing to method
if (invokeDelegate == null && !method.DeclaringType.IsValueType)
{
List<Type> methodTypes = new List<Type>();
if (!method.IsStatic)

View File

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

View File

@@ -87,7 +87,11 @@ bool GPUShader::Create(MemoryReadStream& stream)
GPUShaderProgramInitializer initializer;
#if !BUILD_RELEASE
initializer.Owner = this;
const StringView name = GetName();
#else
const StringView name;
#endif
const bool hasCompute = GPUDevice::Instance->Limits.HasCompute;
for (int32 i = 0; i < shadersCount; i++)
{
const ShaderStage type = static_cast<ShaderStage>(stream.ReadByte());
@@ -117,10 +121,15 @@ bool GPUShader::Create(MemoryReadStream& stream)
stream.ReadBytes(&initializer.Bindings, sizeof(ShaderBindings));
// Create shader program
if (type == ShaderStage::Compute && !hasCompute)
{
LOG(Warning, "Failed to create {} Shader program '{}' ({}).", ::ToString(type), String(initializer.Name), name);
continue;
}
GPUShaderProgram* shader = CreateGPUShaderProgram(type, initializer, cache, cacheSize, stream);
if (shader == nullptr)
{
LOG(Error, "Failed to create {} Shader program '{}'.", ::ToString(type), String(initializer.Name));
LOG(Error, "Failed to create {} Shader program '{}' ({}).", ::ToString(type), String(initializer.Name), name);
return true;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -541,8 +541,8 @@ private:
};
static void callActorEvent(ActorEventType eventType, Actor* a, Actor* b);
static bool loadScene(const Guid& sceneId);
static bool loadScene(const String& scenePath);
// All loadScene assume that ScenesLock has been taken by the calling thread
static bool loadScene(JsonAsset* sceneAsset);
static bool loadScene(const BytesContainer& sceneData, Scene** outScene = nullptr);
static bool loadScene(rapidjson_flax::Document& document, Scene** outScene = nullptr);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -48,6 +48,7 @@ CPUInfo Cpu;
String UserLocale;
double SecondsPerCycle;
NSAutoreleasePool* AutoreleasePool = nullptr;
int32 AutoreleasePoolInterval = 0;
float ApplePlatform::ScreenScale = 1.0f;
@@ -245,13 +246,40 @@ void ApplePlatform::GetUTCTime(int32& year, int32& month, int32& dayOfWeek, int3
millisecond = time.tv_usec / 1000;
}
#if !BUILD_RELEASE
void ApplePlatform::Log(const StringView& msg)
{
#if !BUILD_RELEASE && !USE_EDITOR
#if !USE_EDITOR
NSLog(@"%s", StringAsANSI<>(*msg, msg.Length()).Get());
#endif
}
bool ApplePlatform::IsDebuggerPresent()
{
// Reference: https://developer.apple.com/library/archive/qa/qa1361/_index.html
int mib[4];
struct kinfo_proc info;
// Initialize the flags so that, if sysctl fails for some bizarre reason, we get a predictable result
info.kp_proc.p_flag = 0;
// Initialize mib, which tells sysctl the info we want, in this case we're looking for information about a specific process ID
mib[0] = CTL_KERN;
mib[1] = KERN_PROC;
mib[2] = KERN_PROC_PID;
mib[3] = getpid();
// Call sysctl
size_t size = sizeof(info);
sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0);
// We're being debugged if the P_TRACED flag is set
return ((info.kp_proc.p_flag & P_TRACED) != 0);
}
#endif
bool ApplePlatform::Init()
{
if (UnixPlatform::Init())
@@ -332,9 +360,13 @@ bool ApplePlatform::Init()
void ApplePlatform::Tick()
{
// TODO: do it once every X fames
AutoreleasePoolInterval++;
if (AutoreleasePoolInterval >= 60)
{
AutoreleasePoolInterval = 0;
[AutoreleasePool drain];
AutoreleasePool = [[NSAutoreleasePool alloc] init];
}
}
void ApplePlatform::BeforeExit()
@@ -343,6 +375,11 @@ void ApplePlatform::BeforeExit()
void ApplePlatform::Exit()
{
if (AutoreleasePool)
{
[AutoreleasePool drain];
AutoreleasePool = nullptr;
}
}
void ApplePlatform::SetHighDpiAwarenessEnabled(bool enable)
@@ -369,9 +406,7 @@ bool ApplePlatform::GetHasFocus()
if (window->IsFocused())
return true;
}
// Default to true if has no windows open
return WindowsManager::Windows.IsEmpty();
return false;
}
void ApplePlatform::CreateGuid(Guid& result)

View File

@@ -79,7 +79,10 @@ public:
static uint64 GetClockFrequency();
static void GetSystemTime(int32& year, int32& month, int32& dayOfWeek, int32& day, int32& hour, int32& minute, int32& second, int32& millisecond);
static void GetUTCTime(int32& year, int32& month, int32& dayOfWeek, int32& day, int32& hour, int32& minute, int32& second, int32& millisecond);
#if !BUILD_RELEASE
static void Log(const StringView& msg);
static bool IsDebuggerPresent();
#endif
static bool Init();
static void Tick();
static void BeforeExit();

View File

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

View File

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

View File

@@ -4,7 +4,14 @@
#include "../Window.h"
#include "Engine/Platform/Apple/AppleUtils.h"
#include "Engine/Platform/WindowsManager.h"
#include "Engine/Platform/IGuiData.h"
#if USE_EDITOR
#include "Engine/Platform/CriticalSection.h"
#include "Engine/Threading/ThreadPoolTask.h"
#include "Engine/Threading/ThreadPool.h"
#include "Engine/Engine/Engine.h"
#endif
#include "Engine/Core/Log.h"
#include "Engine/Input/Input.h"
#include "Engine/Input/Mouse.h"
@@ -14,6 +21,37 @@
#include <AppKit/AppKit.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)
{
switch ([event keyCode])
@@ -268,17 +306,20 @@ NSDragOperation GetDragDropOperation(DragDropEffect dragDropEffect)
// Handle resizing to be sure that content has valid size when window was resized
[self windowDidResize:notification];
if (IsWindowInvalid(Window)) return;
Window->OnGotFocus();
}
- (void)windowDidResignKey:(NSNotification*)notification
{
if (IsWindowInvalid(Window)) return;
Window->OnLostFocus();
}
- (void)windowWillClose:(NSNotification*)notification
{
[self setDelegate: nil];
if (IsWindowInvalid(Window)) return;
Window->Close(ClosingReason::User);
}
@@ -311,7 +352,7 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r)
@end
@interface MacViewImpl : NSView
@interface MacViewImpl : NSView <NSDraggingSource, NSPasteboardItemDataProvider>
{
MacWindow* Window;
NSTrackingArea* TrackingArea;
@@ -375,6 +416,7 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r)
- (void)keyDown:(NSEvent*)event
{
if (IsWindowInvalid(Window)) return;
KeyboardKeys key = GetKey(event);
if (key != KeyboardKeys::None)
Input::Keyboard->OnKeyDown(key, Window);
@@ -405,6 +447,7 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r)
- (void)keyUp:(NSEvent*)event
{
if (IsWindowInvalid(Window)) return;
KeyboardKeys key = GetKey(event);
if (key != KeyboardKeys::None)
Input::Keyboard->OnKeyUp(key, Window);
@@ -412,6 +455,7 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r)
- (void)flagsChanged:(NSEvent*)event
{
if (IsWindowInvalid(Window)) return;
int32 modMask;
int32 keyCode = [event keyCode];
if (keyCode == 0x36 || keyCode == 0x37)
@@ -437,6 +481,7 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r)
- (void)scrollWheel:(NSEvent*)event
{
if (IsWindowInvalid(Window)) return;
Float2 mousePos = GetMousePosition(Window, event);
double deltaX = [event scrollingDeltaX];
double deltaY = [event scrollingDeltaY];
@@ -451,32 +496,39 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r)
- (void)mouseMoved:(NSEvent*)event
{
if (IsWindowInvalid(Window)) return;
Float2 mousePos = GetMousePosition(Window, event);
if (!Window->IsMouseTracking() && !IsMouseOver)
if (Window->IsMouseTracking())
return; // Skip mouse events when tracking mouse (handled in MacWindow::OnUpdate)
if (!IsMouseOver)
return;
Input::Mouse->OnMouseMove(Window->ClientToScreen(mousePos), Window);
}
- (void)mouseEntered:(NSEvent*)event
{
if (IsWindowInvalid(Window)) return;
IsMouseOver = true;
Window->SetIsMouseOver(true);
}
- (void)mouseExited:(NSEvent*)event
{
if (IsWindowInvalid(Window)) return;
IsMouseOver = false;
Window->SetIsMouseOver(false);
}
- (void)mouseDown:(NSEvent*)event
{
if (IsWindowInvalid(Window)) return;
Float2 mousePos = GetMousePosition(Window, event);
mousePos = Window->ClientToScreen(mousePos);
MouseButton mouseButton = MouseButton::Left;
if ([event clickCount] == 2)
Input::Mouse->OnMouseDoubleClick(Window->ClientToScreen(mousePos), mouseButton, Window);
Input::Mouse->OnMouseDoubleClick(mousePos, mouseButton, Window);
else
Input::Mouse->OnMouseDown(Window->ClientToScreen(mousePos), mouseButton, Window);
Input::Mouse->OnMouseDown(mousePos, mouseButton, Window);
}
- (void)mouseDragged:(NSEvent*)event
@@ -486,13 +538,28 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r)
- (void)mouseUp:(NSEvent*)event
{
if (IsWindowInvalid(Window)) return;
Float2 mousePos = GetMousePosition(Window, event);
mousePos = Window->ClientToScreen(mousePos);
MouseButton mouseButton = MouseButton::Left;
Input::Mouse->OnMouseUp(Window->ClientToScreen(mousePos), mouseButton, Window);
Input::Mouse->OnMouseUp(mousePos, mouseButton, Window);
// Redirect event to any window that tracks the mouse (eg. dock window in Editor)
WindowsManager::WindowsLocker.Lock();
for (auto* win : WindowsManager::Windows)
{
if (win->IsVisible() && win->IsMouseTracking() && win != Window)
{
Input::Mouse->OnMouseUp(mousePos, mouseButton, win);
break;
}
}
WindowsManager::WindowsLocker.Unlock();
}
- (void)rightMouseDown:(NSEvent*)event
{
if (IsWindowInvalid(Window)) return;
Float2 mousePos = GetMousePosition(Window, event);
MouseButton mouseButton = MouseButton::Right;
if ([event clickCount] == 2)
@@ -508,6 +575,7 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r)
- (void)rightMouseUp:(NSEvent*)event
{
if (IsWindowInvalid(Window)) return;
Float2 mousePos = GetMousePosition(Window, event);
MouseButton mouseButton = MouseButton::Right;
Input::Mouse->OnMouseUp(Window->ClientToScreen(mousePos), mouseButton, Window);
@@ -515,6 +583,7 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r)
- (void)otherMouseDown:(NSEvent*)event
{
if (IsWindowInvalid(Window)) return;
Float2 mousePos = GetMousePosition(Window, event);
MouseButton mouseButton;
switch ([event buttonNumber])
@@ -544,6 +613,7 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r)
- (void)otherMouseUp:(NSEvent*)event
{
if (IsWindowInvalid(Window)) return;
Float2 mousePos = GetMousePosition(Window, event);
MouseButton mouseButton;
switch ([event buttonNumber])
@@ -565,6 +635,7 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r)
- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender
{
if (IsWindowInvalid(Window)) return NSDragOperationNone;
Float2 mousePos;
MacDropData dropData;
GetDragDropData(Window, sender, mousePos, dropData);
@@ -580,6 +651,7 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r)
- (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)sender
{
if (IsWindowInvalid(Window)) return NSDragOperationNone;
Float2 mousePos;
MacDropData dropData;
GetDragDropData(Window, sender, mousePos, dropData);
@@ -590,6 +662,7 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r)
- (BOOL)performDragOperation:(id<NSDraggingInfo>)sender
{
if (IsWindowInvalid(Window)) return NO;
Float2 mousePos;
MacDropData dropData;
GetDragDropData(Window, sender, mousePos, dropData);
@@ -600,9 +673,38 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r)
- (void)draggingExited:(id<NSDraggingInfo>)sender
{
if (IsWindowInvalid(Window)) return;
Window->OnDragLeave();
}
- (NSDragOperation)draggingSession:(NSDraggingSession*)session sourceOperationMaskForDraggingContext:(NSDraggingContext)context
{
if (IsWindowInvalid(Window)) return NSDragOperationNone;
return NSDragOperationMove;
}
- (void)draggingSession:(NSDraggingSession*)session endedAtPoint:(NSPoint)screenPoint operation:(NSDragOperation)operation
{
#if USE_EDITOR
// Stop background worker once the drag ended
MacDragLocker.Lock();
if (MacDragSession && MacDragSession == session)
{
Platform::AtomicStore(&MacDragJob->ExitFlag, 1);
MacDragJob->Wait();
MacDragSession = nullptr;
MacDragJob = nullptr;
}
MacDragLocker.Unlock();
#endif
}
- (void)pasteboard:(nullable NSPasteboard*)pasteboard item:(NSPasteboardItem*)item provideDataForType:(NSPasteboardType)type
{
if (IsWindowInvalid(Window)) return;
[pasteboard setString:(NSString*)AppleUtils::ToString(Window->GetDragText()) forType:NSPasteboardTypeString];
}
@end
MacWindow::MacWindow(const CreateWindowSettings& settings)
@@ -624,6 +726,8 @@ MacWindow::MacWindow(const CreateWindowSettings& settings)
{
styleMask |= NSWindowStyleMaskBorderless;
}
if (settings.Fullscreen)
styleMask |= NSWindowStyleMaskFullScreen;
if (settings.HasBorder)
{
styleMask |= NSWindowStyleMaskTitled;
@@ -650,9 +754,11 @@ MacWindow::MacWindow(const CreateWindowSettings& settings)
[window setMaxSize:NSMakeSize(settings.MaximumSize.X, settings.MaximumSize.Y)];
[window setOpaque:!settings.SupportsTransparency];
[window setContentView:view];
if (settings.AllowInput)
[window setAcceptsMouseMovedEvents:YES];
[window setDelegate:window];
_window = window;
_view = view;
if (settings.AllowDragAndDrop)
{
[view registerForDraggedTypes:@[NSPasteboardTypeFileURL, NSPasteboardTypeString]];
@@ -664,8 +770,6 @@ MacWindow::MacWindow(const CreateWindowSettings& settings)
layer.contentsScale = screenScale;
// TODO: impl Parent for MacWindow
// TODO: impl StartPosition for MacWindow
// TODO: impl Fullscreen for MacWindow
// TODO: impl ShowInTaskbar for MacWindow
// TODO: impl IsTopmost for MacWindow
}
@@ -676,6 +780,7 @@ MacWindow::~MacWindow()
[window close];
[window release];
_window = nullptr;
_view = nullptr;
}
void MacWindow::CheckForResize(float width, float height)
@@ -699,7 +804,6 @@ void MacWindow::SetIsMouseOver(bool value)
// Refresh cursor typet
SetCursor(CursorType::Default);
SetCursor(cursor);
}
else
{
@@ -714,6 +818,22 @@ void* MacWindow::GetNativePtr() const
return _window;
}
void MacWindow::OnUpdate(float dt)
{
if (IsMouseTracking())
{
// Keep sending mouse movement events no matter if window has focus
Float2 mousePos = Platform::GetMousePosition();
if (_mouseTrackPos != mousePos)
{
_mouseTrackPos = mousePos;
Input::Mouse->OnMouseMove(mousePos, this);
}
}
WindowBase::OnUpdate(dt);
}
void MacWindow::Show()
{
if (!_visible)
@@ -728,7 +848,10 @@ void MacWindow::Show()
// Show
NSWindow* window = (NSWindow*)_window;
if (_settings.AllowInput)
[window makeKeyAndOrderFront:window];
else
[window orderFront:window];
if (_settings.ActivateWhenFirstShown)
[NSApp activateIgnoringOtherApps:YES];
_focused = true;
@@ -746,7 +869,7 @@ void MacWindow::Hide()
// Hide
NSWindow* window = (NSWindow*)_window;
[window orderOut:nil];
[window orderOut:window];
// Base
WindowBase::Hide();
@@ -782,14 +905,9 @@ void MacWindow::Restore()
[window zoom:nil];
}
bool MacWindow::IsClosed() const
{
return _window != nullptr;
}
bool MacWindow::IsForegroundWindow() const
{
return Platform::GetHasFocus() && IsFocused();
return IsFocused() && Platform::GetHasFocus();
}
void MacWindow::BringToFront(bool force)
@@ -808,14 +926,13 @@ void MacWindow::SetClientBounds(const Rectangle& clientArea)
NSWindow* window = (NSWindow*)_window;
if (!window)
return;
const float screenScale = MacPlatform::ScreenScale;
NSRect oldRect = [window frame];
NSRect newRect = NSMakeRect(0, 0, clientArea.Size.X, clientArea.Size.Y);
NSRect newRect = NSMakeRect(0, 0, clientArea.Size.X / screenScale, clientArea.Size.Y / screenScale);
newRect = [window frameRectForContentRect:newRect];
//newRect.origin.x = oldRect.origin.x;
//newRect.origin.y = NSMaxY(oldRect) - newRect.size.height;
Float2 pos = AppleUtils::PosToCoca(clientArea.Location);
Float2 pos = AppleUtils::PosToCoca(clientArea.Location) / screenScale;
Float2 titleSize = GetWindowTitleSize(this);
newRect.origin.x = pos.X + titleSize.X;
newRect.origin.y = pos.Y - newRect.size.height + titleSize.Y;
@@ -909,8 +1026,63 @@ void MacWindow::SetTitle(const StringView& title)
DragDropEffect MacWindow::DoDragDrop(const StringView& data)
{
// TODO: implement using beginDraggingSession and NSDraggingSource
return DragDropEffect::None;
NSWindow* window = (NSWindow*)_window;
MacViewImpl* view = (MacViewImpl*)_view;
_dragText = data;
// Create mouse drag event
NSEvent* event = [NSEvent
mouseEventWithType:NSEventTypeLeftMouseDragged
location:window.mouseLocationOutsideOfEventStream
modifierFlags:0
timestamp:NSApp.currentEvent.timestamp
windowNumber:window.windowNumber
context:nil
eventNumber:0
clickCount:1
pressure:1.0];
// Create drag item
NSPasteboardItem* pasteItem = [NSPasteboardItem new];
[pasteItem setDataProvider:view forTypes:[NSArray arrayWithObjects:NSPasteboardTypeString, nil]];
NSDraggingItem* dragItem = [[NSDraggingItem alloc] initWithPasteboardWriter:pasteItem];
[dragItem setDraggingFrame:NSMakeRect(event.locationInWindow.x, event.locationInWindow.y, 1, 1) contents:nil];
// Start dragging session
NSDraggingSession* draggingSession = [view beginDraggingSessionWithItems:[NSArray arrayWithObject:dragItem] event:event source:view];
DragDropEffect result = DragDropEffect::None;
#if USE_EDITOR
// Create background worker that will keep updating GUI (perform rendering)
MacDragLocker.Lock();
ASSERT(!MacDragSession && !MacDragJob);
MacDragSession = draggingSession;
MacDragJob = New<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)

View File

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

View File

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

View File

@@ -16,16 +16,13 @@ class FLAXENGINE_API Win32CriticalSection
friend Win32ConditionVariable;
private:
mutable Windows::CRITICAL_SECTION _criticalSection;
private:
Win32CriticalSection(const Win32CriticalSection&);
Win32CriticalSection& operator=(const Win32CriticalSection&);
public:
/// <summary>
/// Initializes a new instance of the <see cref="Win32CriticalSection"/> class.
/// </summary>
@@ -43,18 +40,13 @@ public:
}
public:
/// <summary>
/// Locks the critical section.
/// </summary>
void Lock() const
{
// Spin first before entering critical section, causing ring-0 transition and context switch
if (Windows::TryEnterCriticalSection(&_criticalSection) == 0)
{
Windows::EnterCriticalSection(&_criticalSection);
}
}
/// <summary>
/// Attempts to enter a critical section without blocking. If the call is successful, the calling thread takes ownership of the critical section.

View File

@@ -451,6 +451,7 @@ DialogResult MessageBox::Show(Window* parent, const StringView& text, const Stri
default:
break;
}
flags |= MB_TASKMODAL;
// Show dialog
int result = MessageBoxW(parent ? static_cast<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
{
return Platform::GetHasFocus() && IsFocused();
return IsFocused() && Platform::GetHasFocus();
}
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)
{
if (!_platformSupportsDoF || checkIfSkipPass())
DepthOfFieldSettings& dofSettings = renderContext.List->Settings.DepthOfField;
const bool useDoF = EnumHasAnyFlags(renderContext.View.Flags, ViewFlags::DepthOfField) && dofSettings.Enabled;
if (!useDoF || !_platformSupportsDoF || checkIfSkipPass())
return;
auto device = GPUDevice::Instance;
auto context = device->GetMainContext();
const auto depthBuffer = renderContext.Buffers->DepthBuffer;
const auto shader = _shader->GetShader();
DepthOfFieldSettings& dofSettings = renderContext.List->Settings.DepthOfField;
const bool useDoF = _platformSupportsDoF && EnumHasAnyFlags(renderContext.View.Flags, ViewFlags::DepthOfField) && dofSettings.Enabled;
if (!useDoF)
return;
PROFILE_GPU_CPU("Depth Of Field");
context->ResetSR();

View File

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

View File

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

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;
// Ensure to have valid data and if at least one effect should be applied
if (checkIfSkipPass() || !(useBloom || useToneMapping || useCameraArtifacts))
if (!(useBloom || useToneMapping || useCameraArtifacts) || checkIfSkipPass())
{
// Resources are missing. Do not perform rendering. Just copy raw frame
context->SetViewportAndScissors((float)output->Width(), (float)output->Height());

View File

@@ -1270,7 +1270,11 @@ bool ManagedBinaryModule::InvokeMethod(void* method, const Variant& instance, Sp
// Invoke the method
MObject* exception = nullptr;
#if USE_NETCORE // NetCore uses the same path for both virtual and non-virtual calls
MObject* resultObject = mMethod->Invoke(mInstance, params, &exception);
#else
MObject* resultObject = withInterfaces ? mMethod->InvokeVirtual((MObject*)mInstance, params, &exception) : mMethod->Invoke(mInstance, params, &exception);
#endif
if (exception)
{
MException ex(exception);

View File

@@ -320,7 +320,7 @@ namespace FlaxEngine
internal static partial Object Internal_Create2(string typeName);
[LibraryImport("FlaxEngine", EntryPoint = "ObjectInternal_ManagedInstanceCreated", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(Interop.StringMarshaller))]
internal static partial void Internal_ManagedInstanceCreated(Object managedInstance, IntPtr theKlass);
internal static partial void Internal_ManagedInstanceCreated(Object managedInstance, IntPtr typeClass);
[LibraryImport("FlaxEngine", EntryPoint = "ObjectInternal_ManagedInstanceDeleted", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(Interop.StringMarshaller))]
internal static partial void Internal_ManagedInstanceDeleted(IntPtr nativeInstance);

View File

@@ -46,6 +46,7 @@
#include <mono/metadata/metadata.h>
#include <mono/metadata/threads.h>
#include <mono/metadata/reflection.h>
#include <mono/metadata/mono-gc.h>
#include <mono/metadata/mono-private-unstable.h>
typedef char char_t;
#define DOTNET_HOST_MONO_DEBUG 0
@@ -522,7 +523,8 @@ void MCore::GC::FreeMemory(void* ptr, bool coTaskMem)
void MCore::Thread::Attach()
{
#if DOTNET_HOST_MONO
// TODO: find a way to properly register native thread so Mono Stop The World (stw) won't freeze when native threads (eg. Job System) are running native code only
#if DOTNET_HOST_MONO && 0
if (!IsInMainThread() && !mono_domain_get())
{
mono_thread_attach(MonoDomainHandle);
@@ -2056,7 +2058,7 @@ bool InitHostfxr()
// Setup debugger
{
int32 debuggerLogLevel = 0;
if (CommandLine::Options.MonoLog.IsTrue())
if (CommandLine::Options.MonoLog.IsTrue() || DOTNET_HOST_MONO_DEBUG)
{
LOG(Info, "Using detailed Mono logging");
mono_trace_set_level_string("debug");
@@ -2139,6 +2141,7 @@ bool InitHostfxr()
LOG(Fatal, "Failed to initialize Mono.");
return true;
}
mono_gc_init_finalizer_thread();
// Log info
char* buildInfo = mono_get_runtime_build_info();

View File

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

Binary file not shown.

View File

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

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

View File

@@ -1,5 +1,6 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using System.Collections.Generic;
using System.IO;
using Flax.Build;
using Flax.Build.NativeCpp;
@@ -62,4 +63,12 @@ public class volk : ThirdPartyModule
Log.ErrorOnce("Missing VulkanSDK.", ref _missingSDKError);
}
}
/// <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();
contents.AppendLine($"#define {binaryModuleNameUpper}_NAME \"{binaryModuleName}\"");
if (version.Build == -1)
if (version.Build <= 0)
contents.AppendLine($"#define {binaryModuleNameUpper}_VERSION Version({version.Major}, {version.Minor})");
else
else if (version.Revision <= 0)
contents.AppendLine($"#define {binaryModuleNameUpper}_VERSION Version({version.Major}, {version.Minor}, {version.Build})");
else
contents.AppendLine($"#define {binaryModuleNameUpper}_VERSION Version({version.Major}, {version.Minor}, {version.Build}, {version.Revision})");
contents.AppendLine($"#define {binaryModuleNameUpper}_VERSION_TEXT \"{version}\"");
contents.AppendLine($"#define {binaryModuleNameUpper}_VERSION_MAJOR {version.Major}");
contents.AppendLine($"#define {binaryModuleNameUpper}_VERSION_MINOR {version.Minor}");
contents.AppendLine($"#define {binaryModuleNameUpper}_VERSION_BUILD {version.Build}");
contents.AppendLine($"#define {binaryModuleNameUpper}_VERSION_REVISION {version.Revision}");
contents.AppendLine($"#define {binaryModuleNameUpper}_COMPANY \"{project.Company}\"");
contents.AppendLine($"#define {binaryModuleNameUpper}_COPYRIGHT \"{project.Copyright}\"");
contents.AppendLine();

View File

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

View File

@@ -127,6 +127,33 @@ namespace Flax.Deploy
// Deploy project
DeployFile(RootPath, OutputPath, "Flax.flaxproj");
// When deploying Editor with Platforms at once then bundle them inside it
if (Configuration.DeployPlatforms && Platforms.PackagedPlatforms != null)
{
foreach (var platform in Platforms.PackagedPlatforms)
{
Log.Info(string.Empty);
Log.Info($"Bunding {platform} platform with Editor");
Log.Info(string.Empty);
string platformName = platform.ToString();
string platformFiles = Path.Combine(Deployer.PackageOutputPath, platformName);
string platformData = Path.Combine(OutputPath, "Source", "Platforms", platformName);
if (Directory.Exists(platformFiles))
{
// Copy deployed files
Utilities.DirectoryCopy(platformFiles, platformData);
}
else
{
// Extract deployed files
var packageZipPath = Path.Combine(Deployer.PackageOutputPath, platformName + ".zip");
Log.Verbose(packageZipPath + " -> " + platformData);
System.IO.Compression.ZipFile.ExtractToDirectory(packageZipPath, platformData, true);
}
}
}
// Package Editor into macOS app
if (Platform.BuildTargetPlatform == TargetPlatform.Mac)
{
@@ -164,12 +191,34 @@ namespace Flax.Deploy
var defaultEditorConfig = "Development";
var ediotrBinariesPath = Path.Combine(appContentsPath, "Binaries/Editor/Mac", defaultEditorConfig);
Utilities.DirectoryCopy(ediotrBinariesPath, appBinariesPath, true, true);
// Sign app resources
CodeSign(appPath);
// Build a disk image
var dmgPath = Path.Combine(Deployer.PackageOutputPath, "FlaxEditor.dmg");
Log.Info(string.Empty);
Log.Info("Building disk image...");
if (File.Exists(dmgPath))
File.Delete(dmgPath);
Utilities.Run("hdiutil", $"create -srcFolder \"{appPath}\" -o \"{dmgPath}\"", null, null, Utilities.RunOptions.Default | Utilities.RunOptions.ThrowExceptionOnError);
CodeSign(dmgPath);
Log.Info("Output disk image size: " + Utilities.GetFileSize(dmgPath));
// Notarize disk image
if (!string.IsNullOrEmpty(Configuration.DeployKeychainProfile))
{
Log.Info(string.Empty);
Log.Info("Notarizing disk image...");
Utilities.Run("xcrun", $"notarytool submit \"{dmgPath}\" --wait --keychain-profile \"{Configuration.DeployKeychainProfile}\"", null, null, Utilities.RunOptions.Default | Utilities.RunOptions.ThrowExceptionOnError);
Utilities.Run("xcrun", $"stapler staple \"{dmgPath}\"", null, null, Utilities.RunOptions.Default | Utilities.RunOptions.ThrowExceptionOnError);
Log.Info("App notarized for macOS distribution!");
}
}
// Compress
if (Configuration.DontCompress)
return;
Log.Info(string.Empty);
Log.Info("Compressing editor files...");
string editorPackageZipPath;
@@ -299,6 +348,7 @@ namespace Flax.Deploy
Utilities.Run("strip", "FlaxEditor.dylib", null, dst, Utilities.RunOptions.None);
Utilities.Run("strip", "libMoltenVK.dylib", null, dst, Utilities.RunOptions.None);
// Sign binaries
CodeSign(Path.Combine(dst, "FlaxEditor"));
CodeSign(Path.Combine(dst, "FlaxEditor.dylib"));
CodeSign(Path.Combine(dst, "libMoltenVK.dylib"));

View File

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

View File

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

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>
public static void CodeSign(string file, string signIdenity)
{
if (!File.Exists(file))
var isDirectory = Directory.Exists(file);
if (!isDirectory && !File.Exists(file))
throw new FileNotFoundException("Missing file to sign.", file);
string cmdLine = string.Format("--force --timestamp -s \"{0}\" \"{1}\"", signIdenity, file);
if (string.IsNullOrEmpty(Path.GetExtension(file)))
if (isDirectory)
{
// Automatically sign contents
cmdLine += " --deep";
}
{
// Enable the hardened runtime
cmdLine += " --options=runtime";
}
{
// Add entitlements file with some settings for the app execution
cmdLine += string.Format(" --entitlements \"{0}\"", Path.Combine(Globals.EngineRoot, "Source/Platforms/Mac/Default.entitlements"));

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