From 3dcd1a5ffb9b6747870aa1fb866c04c1224ebec7 Mon Sep 17 00:00:00 2001 From: Christopher Rothert Date: Wed, 4 Oct 2023 19:07:48 +0200 Subject: [PATCH 01/78] Improve viewport camera settings * remove fixed camera speed buttons from camera speed scale widget * change camera speed scale widget to general camera settings widget * move all camera-related settings from view mode widget to camera settings widget * fix some typo * add possibility to set camera speed manually * add min/max camera speed options --- Source/Editor/Viewport/EditorViewport.cs | 415 +++++++++++++++-------- 1 file changed, 279 insertions(+), 136 deletions(-) diff --git a/Source/Editor/Viewport/EditorViewport.cs b/Source/Editor/Viewport/EditorViewport.cs index 8c71a8b02..83fce7a3d 100644 --- a/Source/Editor/Viewport/EditorViewport.cs +++ b/Source/Editor/Viewport/EditorViewport.cs @@ -125,12 +125,19 @@ namespace FlaxEditor.Viewport public const int FpsCameraFilteringFrames = 3; /// - /// The speed widget button. + /// The camera settings widget. /// - protected ViewportWidgetButton _speedWidget; + protected ViewportWidgetsContainer _cameraWidget; + + /// + /// The camera settings widget button. + /// + protected ViewportWidgetButton _cameraButton; private float _mouseSensitivity; private float _movementSpeed; + private float _minMovementSpeed; + private float _maxMovementSpeed; private float _mouseAccelerationScale; private bool _useMouseFiltering; private bool _useMouseAcceleration; @@ -171,8 +178,8 @@ namespace FlaxEditor.Viewport private float _fieldOfView; private float _nearPlane; private float _farPlane; - private float _orthoSize = 1.0f; - private bool _isOrtho = false; + private float _orthoSize; + private bool _isOrtho; private float _wheelMovementChangeDeltaSum = 0; private bool _invertPanning; @@ -194,19 +201,31 @@ namespace FlaxEditor.Viewport get => _movementSpeed; set { - for (int i = 0; i < EditorViewportCameraSpeedValues.Length; i++) - { - if (Math.Abs(value - EditorViewportCameraSpeedValues[i]) < 0.001f) - { - _movementSpeed = EditorViewportCameraSpeedValues[i]; - if (_speedWidget != null) - _speedWidget.Text = _movementSpeed.ToString(); - break; - } - } + _movementSpeed = value; + + if (_cameraButton != null) + _cameraButton.Text = _movementSpeed.ToString(); } } + /// + /// Gets or sets the minimum camera movement speed. + /// + public float MinMovementSpeed + { + get => _minMovementSpeed; + set => _minMovementSpeed = value; + } + + /// + /// Gets or sets the maximum camera movement speed. + /// + public float MaxMovementSpeed + { + get => _maxMovementSpeed; + set => _maxMovementSpeed = value; + } + /// /// Gets the mouse movement position delta (user press and move). /// @@ -441,30 +460,183 @@ namespace FlaxEditor.Viewport 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) + + // Camera settings widget + _cameraWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight); + + // Camera settings menu + var cameraCM = new ContextMenu(); + _cameraButton = new ViewportWidgetButton(_movementSpeed.ToString(), Editor.Instance.Icons.Camera64, cameraCM) { 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; + + // 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; + + 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, 64.0f, 0.5f) + { + Parent = maxCamSpeedButton, + }; + + minCamSpeedValue.ValueChanged += () => + { + OnMinMovementSpeedChanged(minCamSpeedValue); + + maxCamSpeedValue.MinValue = minCamSpeedValue.Value; + + if (camSpeedValue.MinValue != minCamSpeedValue.Value) + camSpeedValue.MinValue = minCamSpeedValue.Value; + }; + cameraCM.VisibleChanged += control => minCamSpeedValue.Value = _minMovementSpeed; + maxCamSpeedValue.ValueChanged += () => + { + OnMaxMovementSpeedChanged(maxCamSpeedValue); + + minCamSpeedValue.MaxValue = maxCamSpeedValue.Value; + + if (camSpeedValue.MaxValue != maxCamSpeedValue.Value) + camSpeedValue.MaxValue = maxCamSpeedValue.Value; + }; + cameraCM.VisibleChanged += control => maxCamSpeedValue.Value = _maxMovementSpeed; + + // 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 + { + 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; + }; + } + + // Ortho 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; + } + #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) @@ -481,8 +653,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; } } @@ -540,104 +712,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"); @@ -661,6 +735,7 @@ namespace FlaxEditor.Viewport resolutionValue.ValueChanged += () => ResolutionScale = resolutionValue.Value; ViewWidgetButtonMenu.VisibleChanged += control => resolutionValue.Value = ResolutionScale; } + #endregion View mode widget // Invert Panning { @@ -695,6 +770,74 @@ namespace FlaxEditor.Viewport // Link for task event task.Begin += OnRenderBegin; } + + private void OnMovementSpeedChanged(FloatValueBox control) + { + var value = Mathf.Clamp(control.Value, _minMovementSpeed, _maxMovementSpeed); + MovementSpeed = value; + } + + private void OnMinMovementSpeedChanged(FloatValueBox control) + { + var value = Mathf.Clamp(control.Value, 0.05f, _maxMovementSpeed); + _minMovementSpeed = value; + + if (_movementSpeed < value) + _movementSpeed = value; + } + + private void OnMaxMovementSpeedChanged(FloatValueBox control) + { + var value = Mathf.Clamp(control.Value, _minMovementSpeed, 1000.0f); + _maxMovementSpeed = value; + + if (_movementSpeed > value) + _movementSpeed = value; + } + + + private void OnInvertPanningToggled(Control control) + { + _invertPanning = !_invertPanning; + } + + + private void OnViewpointChanged(ContextMenuButton button) + { + var orient = Quaternion.Euler((Float3)button.Tag); + OrientViewport(ref orient); + } + + private void OnFieldOfViewChanged(FloatValueBox control) + { + _fieldOfView = control.Value; + } + + private void OnOrthographicModeToggled(Control control) + { + _isOrtho = !_isOrtho; + + if (_isOrtho) + { + var orient = ViewOrientation; + OrientViewport(ref orient); + } + } + + private void OnOrthographicSizeChanged(FloatValueBox control) + { + _orthoSize = control.Value; + } + + private void OnNearPlaneChanged(FloatValueBox control) + { + _nearPlane = control.Value; + } + + private void OnFarPlaneChanged(FloatValueBox control) + { + _farPlane = control.Value; + } /// /// Gets a value indicating whether this viewport is using mouse currently (eg. user moving objects). @@ -793,7 +936,7 @@ namespace FlaxEditor.Viewport } private FpsCounter _fpsCounter; - private ContextMenuButton _showFpsButon; + private ContextMenuButton _showFpsButton; /// /// Gets or sets a value indicating whether show or hide FPS counter. @@ -805,7 +948,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; } } @@ -1043,8 +1186,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 From 67536f04e9500cc9965343bf971825e87a8d5990 Mon Sep 17 00:00:00 2001 From: Christopher Rothert Date: Wed, 4 Oct 2023 21:05:21 +0200 Subject: [PATCH 02/78] Improve camera panning * add camera panning speed option to camera settings context menu * add relative panning speed based on distance to camera target to camera settings context menu * add relative panning option to camera settings context menu * fix float comparisons * remove invert panning entry from view widget * remove unused show/hide method for camera widget --- Source/Editor/Viewport/Cameras/FPSCamera.cs | 5 +- Source/Editor/Viewport/EditorViewport.cs | 111 ++++++++++++-------- 2 files changed, 74 insertions(+), 42 deletions(-) diff --git a/Source/Editor/Viewport/Cameras/FPSCamera.cs b/Source/Editor/Viewport/Cameras/FPSCamera.cs index e578933cf..bf2e840ea 100644 --- a/Source/Editor/Viewport/Cameras/FPSCamera.cs +++ b/Source/Editor/Viewport/Cameras/FPSCamera.cs @@ -259,7 +259,10 @@ namespace FlaxEditor.Viewport.Cameras // Pan if (input.IsPanning) { - var panningSpeed = 0.8f; + var panningSpeed = (Viewport.RelativePanning) + ? Mathf.Abs((position - TargetPoint).Length) * 0.005f + : Viewport.PanningSpeed; + if (Viewport.InvertPanning) { position += up * (mouseDelta.Y * panningSpeed); diff --git a/Source/Editor/Viewport/EditorViewport.cs b/Source/Editor/Viewport/EditorViewport.cs index 83fce7a3d..28546d6df 100644 --- a/Source/Editor/Viewport/EditorViewport.cs +++ b/Source/Editor/Viewport/EditorViewport.cs @@ -181,6 +181,8 @@ namespace FlaxEditor.Viewport private float _orthoSize; private bool _isOrtho; private float _wheelMovementChangeDeltaSum = 0; + private float _panningSpeed; + private bool _relativePanning; private bool _invertPanning; /// @@ -412,6 +414,15 @@ namespace FlaxEditor.Viewport set => _isOrtho = value; } + /// + /// Gets or sets if the panning speed should be relative to the camera target. + /// + public bool RelativePanning + { + get => _relativePanning; + set => _relativePanning = value; + } + /// /// Gets or sets if the panning direction is inverted. /// @@ -421,6 +432,15 @@ namespace FlaxEditor.Viewport set => _invertPanning = value; } + /// + /// Gets or sets the camera panning speed. + /// + public float PanningSpeed + { + get => _panningSpeed; + set => _panningSpeed = value; + } + /// /// The input actions collection to processed during user input. /// @@ -508,7 +528,7 @@ namespace FlaxEditor.Viewport maxCamSpeedValue.MinValue = minCamSpeedValue.Value; - if (camSpeedValue.MinValue != minCamSpeedValue.Value) + if (Math.Abs(camSpeedValue.MinValue - minCamSpeedValue.Value) > Mathf.Epsilon) camSpeedValue.MinValue = minCamSpeedValue.Value; }; cameraCM.VisibleChanged += control => minCamSpeedValue.Value = _minMovementSpeed; @@ -518,11 +538,48 @@ namespace FlaxEditor.Viewport minCamSpeedValue.MaxValue = maxCamSpeedValue.Value; - if (camSpeedValue.MaxValue != maxCamSpeedValue.Value) + if (Math.Abs(camSpeedValue.MaxValue - maxCamSpeedValue.Value) > Mathf.Epsilon) camSpeedValue.MaxValue = maxCamSpeedValue.Value; }; cameraCM.VisibleChanged += control => maxCamSpeedValue.Value = _maxMovementSpeed; - + + // 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"); @@ -736,25 +793,6 @@ namespace FlaxEditor.Viewport ViewWidgetButtonMenu.VisibleChanged += control => resolutionValue.Value = ResolutionScale; } #endregion View mode widget - - // 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; - } } InputActions.Add(options => options.ViewpointTop, () => OrientViewport(Quaternion.Euler(EditorViewportCameraViewpointValues.First(vp => vp.Name == "Top").Orientation))); @@ -794,7 +832,16 @@ namespace FlaxEditor.Viewport if (_movementSpeed > value) _movementSpeed = value; } - + + private void OnPanningSpeedChanged(FloatValueBox control) + { + _panningSpeed = control.Value; + } + + private void OnRelativePanningToggled(Control control) + { + _relativePanning = !_relativePanning; + } private void OnInvertPanningToggled(Control control) { @@ -1636,24 +1683,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) From 4f93a0d9c9c326f9a7b0bcc0125e8dc4bae0999f Mon Sep 17 00:00:00 2001 From: Christopher Rothert Date: Wed, 4 Oct 2023 22:07:30 +0200 Subject: [PATCH 03/78] Add default camera settings to editor options * update ViewportOptions to include new default camera settings * update existing default settings in ViewportOptions * extract viewport options setup into own method * initialize new camera settings with default settings in EditorViewport --- Source/Editor/Options/ViewportOptions.cs | 64 ++++++++++++++++++++---- Source/Editor/Viewport/EditorViewport.cs | 31 +++++++++--- 2 files changed, 76 insertions(+), 19 deletions(-) diff --git a/Source/Editor/Options/ViewportOptions.cs b/Source/Editor/Options/ViewportOptions.cs index cee63a562..c780ac06a 100644 --- a/Source/Editor/Options/ViewportOptions.cs +++ b/Source/Editor/Options/ViewportOptions.cs @@ -24,47 +24,89 @@ namespace FlaxEditor.Options [DefaultValue(1.0f), Limit(0.01f, 100.0f)] [EditorDisplay("General"), EditorOrder(101), Tooltip("The mouse wheel sensitivity applied to zoom in orthographic mode.")] public float MouseWheelSensitivity { get; set; } = 1.0f; - + /// - /// Gets or sets the default movement speed for the viewport camera (must match the dropdown menu values in the viewport). + /// Gets or sets the default movement speed for the viewport camera (must be in range between minimum and maximum movement speed values). /// - [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).")] + [DefaultValue(1.0f), Limit(0.05f, 64.0f)] + [EditorDisplay("Defaults"), EditorOrder(110), Tooltip("The default movement speed for the viewport camera (must be in range between minimum and maximum movement speed values).")] public float DefaultMovementSpeed { get; set; } = 1.0f; + /// + /// Gets or sets the default minimum camera movement speed. + /// + [DefaultValue(0.05f), Limit(0.05f, 64.0f)] + [EditorDisplay("Defaults"), EditorOrder(111), Tooltip("The default minimum movement speed for the viewport camera.")] + public float DefaultMinMovementSpeed { get; set; } = 0.05f; + + /// + /// Gets or sets the default maximum camera movement speed. + /// + [DefaultValue(64.0f), Limit(32.0f, 1000.0f)] + [EditorDisplay("Defaults"), EditorOrder(112), Tooltip("The default maximum movement speed for the viewport camera.")] + public float DefaultMaxMovementSpeed { get; set; } = 64f; + /// /// Gets or sets the default near clipping plane distance for the viewport camera. /// [DefaultValue(10.0f), Limit(0.001f, 1000.0f)] - [EditorDisplay("Defaults"), EditorOrder(120), Tooltip("The default near clipping plane distance for the viewport camera.")] + [EditorDisplay("Defaults"), EditorOrder(130), Tooltip("The default near clipping plane distance for the viewport camera.")] public float DefaultNearPlane { get; set; } = 10.0f; /// /// Gets or sets the default far clipping plane distance for the viewport camera. /// [DefaultValue(40000.0f), Limit(10.0f)] - [EditorDisplay("Defaults"), EditorOrder(130), Tooltip("The default far clipping plane distance for the viewport camera.")] + [EditorDisplay("Defaults"), EditorOrder(140), Tooltip("The default far clipping plane distance for the viewport camera.")] public float DefaultFarPlane { get; set; } = 40000.0f; /// /// Gets or sets the default field of view angle (in degrees) for the viewport camera. /// [DefaultValue(60.0f), Limit(35.0f, 160.0f, 0.1f)] - [EditorDisplay("Defaults", "Default Field Of View"), EditorOrder(140), Tooltip("The default field of view angle (in degrees) for the viewport camera.")] + [EditorDisplay("Defaults"), EditorOrder(150), Tooltip("The default field of view angle (in degrees) for the viewport camera.")] public float DefaultFieldOfView { get; set; } = 60.0f; /// - /// Gets or sets if the panning direction is inverted for the viewport camera. + /// Gets or sets the default camera orthographic mode. /// [DefaultValue(false)] - [EditorDisplay("Defaults"), EditorOrder(150), Tooltip("Invert the panning direction for the viewport camera.")] + [EditorDisplay("Defaults"), EditorOrder(160), Tooltip("The default camera orthographic mode.")] + public bool DefaultOrthographicProjection { get; set; } = false; + + /// + /// Gets or sets the default camera orthographic scale (if camera uses orthographic mode). + /// + [DefaultValue(5.0f), Limit(0.001f, 100000.0f, 0.1f)] + [EditorDisplay("Defaults"), EditorOrder(170), Tooltip("The default camera orthographic scale (if camera uses orthographic mode).")] + public float DefaultOrthographicScale { get; set; } = 5.0f; + + /// + /// Gets or sets the default panning direction for the viewport camera. + /// + [DefaultValue(false)] + [EditorDisplay("Defaults"), EditorOrder(180), Tooltip("The default panning direction for the viewport camera.")] public bool DefaultInvertPanning { get; set; } = false; /// - /// Scales editor viewport grid. + /// Gets or sets the default relative panning mode. + /// + [DefaultValue(true)] + [EditorDisplay("Defaults"), EditorOrder(190), Tooltip("The default relative panning mode. Uses distance between camera and target to determine panning speed.")] + public bool DefaultRelativePanning { get; set; } = true; + + /// + /// Gets or sets the default panning speed (ignored if relative panning is speed enabled). + /// + [DefaultValue(0.8f), Limit(0.01f, 128.0f, 0.1f)] + [EditorDisplay("Defaults"), EditorOrder(200), Tooltip("The default camera panning speed (ignored if relative panning is enabled).")] + public float DefaultPanningSpeed { get; set; } = 0.8f; + + /// + /// Gets or sets the default editor viewport grid scale. /// [DefaultValue(50.0f), Limit(25.0f, 500.0f, 5.0f)] - [EditorDisplay("Defaults"), EditorOrder(160), Tooltip("Scales editor viewport grid.")] + [EditorDisplay("Defaults"), EditorOrder(210), Tooltip("The default editor viewport grid scale.")] public float ViewportGridScale { get; set; } = 50.0f; } } diff --git a/Source/Editor/Viewport/EditorViewport.cs b/Source/Editor/Viewport/EditorViewport.cs index 28546d6df..a85319298 100644 --- a/Source/Editor/Viewport/EditorViewport.cs +++ b/Source/Editor/Viewport/EditorViewport.cs @@ -467,15 +467,8 @@ 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(); } if (useWidgets) @@ -808,6 +801,28 @@ namespace FlaxEditor.Viewport // Link for task event task.Begin += OnRenderBegin; } + + /// + /// Sets the viewport options to the default values. + /// + private void SetupViewportOptions() + { + var options = Editor.Instance.Options.Options; + _minMovementSpeed = options.Viewport.DefaultMinMovementSpeed; + _movementSpeed = options.Viewport.DefaultMovementSpeed; + _maxMovementSpeed = options.Viewport.DefaultMaxMovementSpeed; + _panningSpeed = options.Viewport.DefaultPanningSpeed; + _invertPanning = options.Viewport.DefaultInvertPanning; + _relativePanning = options.Viewport.DefaultRelativePanning; + + _isOrtho = options.Viewport.DefaultOrthographicProjection; + _orthoSize = options.Viewport.DefaultOrthographicScale; + _fieldOfView = options.Viewport.DefaultFieldOfView; + _nearPlane = options.Viewport.DefaultNearPlane; + _farPlane = options.Viewport.DefaultFarPlane; + + OnEditorOptionsChanged(options); + } private void OnMovementSpeedChanged(FloatValueBox control) { From a280ce3dc709875041bd8a5003226fe71145370d Mon Sep 17 00:00:00 2001 From: Christopher Rothert Date: Wed, 4 Oct 2023 22:12:05 +0200 Subject: [PATCH 04/78] Add reset button to camera settings widget --- Source/Editor/Viewport/EditorViewport.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Source/Editor/Viewport/EditorViewport.cs b/Source/Editor/Viewport/EditorViewport.cs index a85319298..da327a665 100644 --- a/Source/Editor/Viewport/EditorViewport.cs +++ b/Source/Editor/Viewport/EditorViewport.cs @@ -680,6 +680,22 @@ namespace FlaxEditor.Viewport 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(); + + // trigger UI update + minCamSpeedValue.Value = _minMovementSpeed; + camSpeedValue.Value = _movementSpeed; + maxCamSpeedValue.Value = _maxMovementSpeed; + }; + } #endregion Camera settings widget #region View mode widget From ccf37e9d6861086f2e129832889484d73972f9ba Mon Sep 17 00:00:00 2001 From: Christopher Rothert Date: Wed, 4 Oct 2023 22:19:48 +0200 Subject: [PATCH 05/78] Add saving and loading of cached values for camera settings * remove "Default" from all settings in ViewportOptions as they are shown in the "Defaults" group anyway --- Source/Editor/Options/ViewportOptions.cs | 26 ++++----- Source/Editor/Viewport/EditorViewport.cs | 69 +++++++++++++++++++----- 2 files changed, 69 insertions(+), 26 deletions(-) diff --git a/Source/Editor/Options/ViewportOptions.cs b/Source/Editor/Options/ViewportOptions.cs index c780ac06a..94e3ee1fc 100644 --- a/Source/Editor/Options/ViewportOptions.cs +++ b/Source/Editor/Options/ViewportOptions.cs @@ -24,83 +24,83 @@ namespace FlaxEditor.Options [DefaultValue(1.0f), Limit(0.01f, 100.0f)] [EditorDisplay("General"), EditorOrder(101), Tooltip("The mouse wheel sensitivity applied to zoom in orthographic mode.")] public float MouseWheelSensitivity { get; set; } = 1.0f; - + /// /// Gets or sets the default movement speed for the viewport camera (must be in range between minimum and maximum movement speed values). /// [DefaultValue(1.0f), Limit(0.05f, 64.0f)] [EditorDisplay("Defaults"), EditorOrder(110), Tooltip("The default movement speed for the viewport camera (must be in range between minimum and maximum movement speed values).")] - public float DefaultMovementSpeed { get; set; } = 1.0f; + public float MovementSpeed { get; set; } = 1.0f; /// /// Gets or sets the default minimum camera movement speed. /// [DefaultValue(0.05f), Limit(0.05f, 64.0f)] [EditorDisplay("Defaults"), EditorOrder(111), Tooltip("The default minimum movement speed for the viewport camera.")] - public float DefaultMinMovementSpeed { get; set; } = 0.05f; + public float MinMovementSpeed { get; set; } = 0.05f; /// /// Gets or sets the default maximum camera movement speed. /// [DefaultValue(64.0f), Limit(32.0f, 1000.0f)] [EditorDisplay("Defaults"), EditorOrder(112), Tooltip("The default maximum movement speed for the viewport camera.")] - public float DefaultMaxMovementSpeed { get; set; } = 64f; - + public float MaxMovementSpeed { get; set; } = 64f; + /// /// Gets or sets the default near clipping plane distance for the viewport camera. /// [DefaultValue(10.0f), Limit(0.001f, 1000.0f)] [EditorDisplay("Defaults"), EditorOrder(130), Tooltip("The default near clipping plane distance for the viewport camera.")] - public float DefaultNearPlane { get; set; } = 10.0f; + public float NearPlane { get; set; } = 10.0f; /// /// Gets or sets the default far clipping plane distance for the viewport camera. /// [DefaultValue(40000.0f), Limit(10.0f)] [EditorDisplay("Defaults"), EditorOrder(140), Tooltip("The default far clipping plane distance for the viewport camera.")] - public float DefaultFarPlane { get; set; } = 40000.0f; + public float FarPlane { get; set; } = 40000.0f; /// /// Gets or sets the default field of view angle (in degrees) for the viewport camera. /// [DefaultValue(60.0f), Limit(35.0f, 160.0f, 0.1f)] [EditorDisplay("Defaults"), EditorOrder(150), Tooltip("The default field of view angle (in degrees) for the viewport camera.")] - public float DefaultFieldOfView { get; set; } = 60.0f; + public float FieldOfView { get; set; } = 60.0f; /// /// Gets or sets the default camera orthographic mode. /// [DefaultValue(false)] [EditorDisplay("Defaults"), EditorOrder(160), Tooltip("The default camera orthographic mode.")] - public bool DefaultOrthographicProjection { get; set; } = false; + public bool OrthographicProjection { get; set; } = false; /// /// Gets or sets the default camera orthographic scale (if camera uses orthographic mode). /// [DefaultValue(5.0f), Limit(0.001f, 100000.0f, 0.1f)] [EditorDisplay("Defaults"), EditorOrder(170), Tooltip("The default camera orthographic scale (if camera uses orthographic mode).")] - public float DefaultOrthographicScale { get; set; } = 5.0f; + public float OrthographicScale { get; set; } = 5.0f; /// /// Gets or sets the default panning direction for the viewport camera. /// [DefaultValue(false)] [EditorDisplay("Defaults"), EditorOrder(180), Tooltip("The default panning direction for the viewport camera.")] - public bool DefaultInvertPanning { get; set; } = false; + public bool InvertPanning { get; set; } = false; /// /// Gets or sets the default relative panning mode. /// [DefaultValue(true)] [EditorDisplay("Defaults"), EditorOrder(190), Tooltip("The default relative panning mode. Uses distance between camera and target to determine panning speed.")] - public bool DefaultRelativePanning { get; set; } = true; + public bool RelativePanning { get; set; } = true; /// /// Gets or sets the default panning speed (ignored if relative panning is speed enabled). /// [DefaultValue(0.8f), Limit(0.01f, 128.0f, 0.1f)] [EditorDisplay("Defaults"), EditorOrder(200), Tooltip("The default camera panning speed (ignored if relative panning is enabled).")] - public float DefaultPanningSpeed { get; set; } = 0.8f; + public float PanningSpeed { get; set; } = 0.8f; /// /// Gets or sets the default editor viewport grid scale. diff --git a/Source/Editor/Viewport/EditorViewport.cs b/Source/Editor/Viewport/EditorViewport.cs index da327a665..a5abfd2b7 100644 --- a/Source/Editor/Viewport/EditorViewport.cs +++ b/Source/Editor/Viewport/EditorViewport.cs @@ -134,6 +134,8 @@ namespace FlaxEditor.Viewport /// protected ViewportWidgetButton _cameraButton; + private readonly Editor _editor; + private float _mouseSensitivity; private float _movementSpeed; private float _minMovementSpeed; @@ -455,6 +457,8 @@ namespace FlaxEditor.Viewport public EditorViewport(SceneRenderTask task, ViewportCamera camera, bool useWidgets) : base(task) { + _editor = Editor.Instance; + _mouseAccelerationScale = 0.1f; _useMouseFiltering = false; _useMouseAcceleration = false; @@ -471,6 +475,30 @@ namespace FlaxEditor.Viewport 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("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); + if (useWidgets) { #region Camera settings widget @@ -510,7 +538,7 @@ namespace FlaxEditor.Viewport }; var maxCamSpeedButton = cameraCM.AddButton("Max Cam Speed"); maxCamSpeedButton.CloseMenuOnClick = false; - var maxCamSpeedValue = new FloatValueBox(_maxMovementSpeed, xLocationForExtras, 2, 70.0f, _minMovementSpeed, 64.0f, 0.5f) + var maxCamSpeedValue = new FloatValueBox(_maxMovementSpeed, xLocationForExtras, 2, 70.0f, _minMovementSpeed, 1000.0f, 0.5f) { Parent = maxCamSpeedButton, }; @@ -824,26 +852,28 @@ namespace FlaxEditor.Viewport private void SetupViewportOptions() { var options = Editor.Instance.Options.Options; - _minMovementSpeed = options.Viewport.DefaultMinMovementSpeed; - _movementSpeed = options.Viewport.DefaultMovementSpeed; - _maxMovementSpeed = options.Viewport.DefaultMaxMovementSpeed; - _panningSpeed = options.Viewport.DefaultPanningSpeed; - _invertPanning = options.Viewport.DefaultInvertPanning; - _relativePanning = options.Viewport.DefaultRelativePanning; + _minMovementSpeed = options.Viewport.MinMovementSpeed; + _movementSpeed = options.Viewport.MovementSpeed; + _maxMovementSpeed = options.Viewport.MaxMovementSpeed; + _panningSpeed = options.Viewport.PanningSpeed; + _invertPanning = options.Viewport.InvertPanning; + _relativePanning = options.Viewport.RelativePanning; - _isOrtho = options.Viewport.DefaultOrthographicProjection; - _orthoSize = options.Viewport.DefaultOrthographicScale; - _fieldOfView = options.Viewport.DefaultFieldOfView; - _nearPlane = options.Viewport.DefaultNearPlane; - _farPlane = options.Viewport.DefaultFarPlane; + _isOrtho = options.Viewport.OrthographicProjection; + _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; + + _editor.ProjectCache.SetCustomData("CameraMovementSpeedValue", _movementSpeed.ToString()); } private void OnMinMovementSpeedChanged(FloatValueBox control) @@ -853,6 +883,8 @@ namespace FlaxEditor.Viewport if (_movementSpeed < value) _movementSpeed = value; + + _editor.ProjectCache.SetCustomData("CameraMinMovementSpeedValue", _minMovementSpeed.ToString()); } private void OnMaxMovementSpeedChanged(FloatValueBox control) @@ -862,21 +894,26 @@ namespace FlaxEditor.Viewport if (_movementSpeed > value) _movementSpeed = value; + + _editor.ProjectCache.SetCustomData("CameraMaxMovementSpeedValue", _maxMovementSpeed.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()); } @@ -889,6 +926,7 @@ namespace FlaxEditor.Viewport private void OnFieldOfViewChanged(FloatValueBox control) { _fieldOfView = control.Value; + _editor.ProjectCache.SetCustomData("CameraFieldOfViewValue", _fieldOfView.ToString()); } private void OnOrthographicModeToggled(Control control) @@ -900,21 +938,26 @@ namespace FlaxEditor.Viewport 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()); } /// From 1f2d665654f74b1791a26d8ac17c7fdc2f99d299 Mon Sep 17 00:00:00 2001 From: Christopher Rothert Date: Wed, 4 Oct 2023 23:06:27 +0200 Subject: [PATCH 06/78] Add camera speed easing based on Mathf.InterpEaseInOut * change camera move speed method to use easing curve * add methods for inversion of Mathf.InterpEaseInOut (might have better alternative) * add setting for easing curve degree in ViewportOptions * update camera speed adjustments to use easing curve * remove unused camera speed values array * update some comments and namings --- Source/Editor/Options/ViewportOptions.cs | 7 ++ Source/Editor/Viewport/EditorViewport.cs | 139 +++++++++++++---------- 2 files changed, 83 insertions(+), 63 deletions(-) diff --git a/Source/Editor/Options/ViewportOptions.cs b/Source/Editor/Options/ViewportOptions.cs index 94e3ee1fc..26caa2694 100644 --- a/Source/Editor/Options/ViewportOptions.cs +++ b/Source/Editor/Options/ViewportOptions.cs @@ -46,6 +46,13 @@ namespace FlaxEditor.Options [EditorDisplay("Defaults"), EditorOrder(112), Tooltip("The default maximum movement speed for the viewport camera.")] public float MaxMovementSpeed { get; set; } = 64f; + /// + /// Gets or sets the degree to which the camera will be eased when using camera flight in the editor window. + /// + [DefaultValue(3.0f), Limit(1.0f, 8.0f)] + [EditorDisplay("Defaults"), EditorOrder(120), Tooltip("The default degree to which the camera will be eased when using camera flight in the editor window.")] + public float CameraEasingDegree { get; set; } = 3.0f; + /// /// Gets or sets the default near clipping plane distance for the viewport camera. /// diff --git a/Source/Editor/Viewport/EditorViewport.cs b/Source/Editor/Viewport/EditorViewport.cs index a5abfd2b7..51b3a2d78 100644 --- a/Source/Editor/Viewport/EditorViewport.cs +++ b/Source/Editor/Viewport/EditorViewport.cs @@ -182,11 +182,14 @@ namespace FlaxEditor.Viewport private float _farPlane; private float _orthoSize; private bool _isOrtho; - private float _wheelMovementChangeDeltaSum = 0; + private float _cameraEasingDegree; private float _panningSpeed; private bool _relativePanning; private bool _invertPanning; + private float _linearMovementProgress; + private float _easedMovementProgress; + /// /// Speed of the mouse. /// @@ -499,6 +502,8 @@ namespace FlaxEditor.Viewport if (_editor.ProjectCache.TryGetCustomData("CameraFarPlaneValue", out cachedState)) _farPlane = float.Parse(cachedState); + OnCameraMovementProgressChanged(); + if (useWidgets) { #region Camera settings widget @@ -530,6 +535,7 @@ namespace FlaxEditor.Viewport 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) @@ -564,7 +570,7 @@ namespace FlaxEditor.Viewport }; cameraCM.VisibleChanged += control => maxCamSpeedValue.Value = _maxMovementSpeed; - // Panning Speed + // Panning speed { var panningSpeed = cameraCM.AddButton("Panning Speed"); panningSpeed.CloseMenuOnClick = false; @@ -581,7 +587,7 @@ namespace FlaxEditor.Viewport }; } - // Relative Panning + // Relative panning { var relativePanning = cameraCM.AddButton("Relative Panning"); relativePanning.CloseMenuOnClick = false; @@ -601,7 +607,7 @@ namespace FlaxEditor.Viewport cameraCM.VisibleChanged += control => relativePanningValue.Checked = _relativePanning; } - // Invert Panning + // Invert panning { var invertPanning = cameraCM.AddButton("Invert Panning"); invertPanning.CloseMenuOnClick = false; @@ -616,7 +622,7 @@ namespace FlaxEditor.Viewport cameraCM.AddSeparator(); - // Camera Viewpoints + // Camera viewpoints { var cameraView = cameraCM.AddChildMenu("Viewpoints").ContextMenu; for (int i = 0; i < EditorViewportCameraViewpointValues.Length; i++) @@ -629,7 +635,7 @@ namespace FlaxEditor.Viewport cameraView.ButtonClicked += OnViewpointChanged; } - // Orthographic + // Orthographic mode { var ortho = cameraCM.AddButton("Orthographic"); ortho.CloseMenuOnClick = false; @@ -649,7 +655,7 @@ namespace FlaxEditor.Viewport cameraCM.VisibleChanged += control => orthoValue.Checked = _isOrtho; } - // Field of View + // Field of view { var fov = cameraCM.AddButton("Field Of View"); fov.CloseMenuOnClick = false; @@ -666,7 +672,7 @@ namespace FlaxEditor.Viewport }; } - // Ortho Scale + // Orthographic scale { var orthoSize = cameraCM.AddButton("Ortho Scale"); orthoSize.CloseMenuOnClick = false; @@ -683,7 +689,7 @@ namespace FlaxEditor.Viewport }; } - // Near Plane + // Near plane { var nearPlane = cameraCM.AddButton("Near Plane"); nearPlane.CloseMenuOnClick = false; @@ -696,7 +702,7 @@ namespace FlaxEditor.Viewport cameraCM.VisibleChanged += control => nearPlaneValue.Value = _nearPlane; } - // Far Plane + // Far plane { var farPlane = cameraCM.AddButton("Far Plane"); farPlane.CloseMenuOnClick = false; @@ -711,7 +717,7 @@ namespace FlaxEditor.Viewport cameraCM.AddSeparator(); - //Reset Button + // Reset button { var reset = cameraCM.AddButton("Reset to default"); reset.ButtonClicked += button => @@ -752,7 +758,7 @@ namespace FlaxEditor.Viewport } } - // View Flags + // View flags { var viewFlags = ViewWidgetButtonMenu.AddChildMenu("View Flags").ContextMenu; viewFlags.AddButton("Reset flags", () => Task.ViewFlags = ViewFlags.DefaultEditor).Icon = Editor.Instance.Icons.Rotate32; @@ -776,7 +782,7 @@ namespace FlaxEditor.Viewport viewFlags.VisibleChanged += WidgetViewFlagsShowHide; } - // Debug View + // Debug view { var debugView = ViewWidgetButtonMenu.AddChildMenu("Debug View").ContextMenu; for (int i = 0; i < EditorViewportViewModeValues.Length; i++) @@ -839,8 +845,8 @@ namespace FlaxEditor.Viewport InputActions.Add(options => options.ViewpointRight, () => OrientViewport(Quaternion.Euler(EditorViewportCameraViewpointValues.First(vp => vp.Name == "Right").Orientation))); InputActions.Add(options => options.ViewpointLeft, () => OrientViewport(Quaternion.Euler(EditorViewportCameraViewpointValues.First(vp => vp.Name == "Left").Orientation))); InputActions.Add(options => options.CameraToggleRotation, () => _isVirtualMouseRightDown = !_isVirtualMouseRightDown); - InputActions.Add(options => options.CameraIncreaseMoveSpeed, () => AdjustCameraMoveSpeed(1)); - InputActions.Add(options => options.CameraDecreaseMoveSpeed, () => AdjustCameraMoveSpeed(-1)); + InputActions.Add(options => options.CameraIncreaseMoveSpeed, () => AdjustCameraMoveSpeed(Editor.Instance.Options.Options.Viewport.MouseWheelSensitivity * 0.01f)); + InputActions.Add(options => options.CameraDecreaseMoveSpeed, () => AdjustCameraMoveSpeed(Editor.Instance.Options.Options.Viewport.MouseWheelSensitivity * -0.01f)); // Link for task event task.Begin += OnRenderBegin; @@ -873,6 +879,7 @@ namespace FlaxEditor.Viewport var value = Mathf.Clamp(control.Value, _minMovementSpeed, _maxMovementSpeed); MovementSpeed = value; + OnCameraMovementProgressChanged(); _editor.ProjectCache.SetCustomData("CameraMovementSpeedValue", _movementSpeed.ToString()); } @@ -884,6 +891,7 @@ namespace FlaxEditor.Viewport if (_movementSpeed < value) _movementSpeed = value; + OnCameraMovementProgressChanged(); _editor.ProjectCache.SetCustomData("CameraMinMovementSpeedValue", _minMovementSpeed.ToString()); } @@ -895,6 +903,7 @@ namespace FlaxEditor.Viewport if (_movementSpeed > value) _movementSpeed = value; + OnCameraMovementProgressChanged(); _editor.ProjectCache.SetCustomData("CameraMaxMovementSpeedValue", _maxMovementSpeed.ToString()); } @@ -992,33 +1001,63 @@ namespace FlaxEditor.Viewport } } + /// + /// The inverse of .
+ /// Interpolate between A and B, applying a reverse ease in function. Exponent controls the degree of the curve. + ///
+ private float InterpInverseEaseIn(float a, float b, float alpha, float exponent) + { + return 0.5f * Mathf.Pow((2.0f * (alpha - a) / (b - a)), 1.0f / exponent); + } + + /// + /// The inverse of .
+ /// Interpolate between A and B, applying a reverse ease out function. Exponent controls the degree of the curve. + ///
+ private float InterpInverseEaseOut(float a, float b, float alpha, float exponent) + { + return 1.0f - InterpInverseEaseIn(a, b, 1.0f - alpha, exponent); + } + + /// + /// The inverse of .
+ /// Interpolate between A and B, applying a reverse ease in/out function. Exponent controls the degree of the curve. + ///
+ private float InterpInverseEaseInOut(float a, float b, float alpha, float exponent) + { + if (alpha <= 0.0f) + return a; + if (alpha >= 1.0f) + return b; + + return (alpha < 0.5f) ? InterpInverseEaseIn(a, b, alpha, exponent) : InterpInverseEaseOut(a, b, alpha, exponent); + } + + private void OnCameraMovementProgressChanged() + { + _linearMovementProgress = Math.Abs(_minMovementSpeed - _maxMovementSpeed) < Mathf.Epsilon + ? 0.0f + : Mathf.Remap(_movementSpeed, _minMovementSpeed, _maxMovementSpeed, 0.0f, 1.0f); + + _easedMovementProgress = InterpInverseEaseInOut(0.0f, 1.0f, _linearMovementProgress, _cameraEasingDegree); + } + /// /// Increases or decreases the camera movement speed. /// - /// The stepping direction for speed adjustment. - protected void AdjustCameraMoveSpeed(int step) + /// The difference in camera speed adjustment as a fraction of 1. + protected void AdjustCameraMoveSpeed(float speedDelta) { - int camValueIndex = -1; - for (int i = 0; i < EditorViewportCameraSpeedValues.Length; i++) - { - if (Mathf.NearEqual(EditorViewportCameraSpeedValues[i], _movementSpeed)) - { - camValueIndex = i; - break; - } - } - if (camValueIndex == -1) - return; - - if (step > 0) - MovementSpeed = EditorViewportCameraSpeedValues[Mathf.Min(camValueIndex + 1, EditorViewportCameraSpeedValues.Length - 1)]; - else if (step < 0) - MovementSpeed = EditorViewportCameraSpeedValues[Mathf.Max(camValueIndex - 1, 0)]; + _easedMovementProgress = Mathf.Clamp(_easedMovementProgress + speedDelta, 0.0f, 1.0f); + var easedSpeed = Mathf.InterpEaseInOut(_minMovementSpeed, _maxMovementSpeed, _easedMovementProgress, _cameraEasingDegree); + MovementSpeed = Mathf.Round(easedSpeed * 100) / 100; } private void OnEditorOptionsChanged(EditorOptions options) { _mouseSensitivity = options.Viewport.MouseSensitivity; + _cameraEasingDegree = options.Viewport.CameraEasingDegree; + OnCameraMovementProgressChanged(); } private void OnRenderBegin(RenderTask task, GPUContext context) @@ -1210,8 +1249,6 @@ namespace FlaxEditor.Viewport /// The parent window. protected virtual void OnControlMouseBegin(Window win) { - _wheelMovementChangeDeltaSum = 0; - // Hide cursor and start tracking mouse movement win.StartTrackingMouse(false); win.Cursor = CursorType.Hidden; @@ -1406,18 +1443,10 @@ 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); - } + // speed delta can be adjusted through mouse wheel sensitivity in editor + // with default sensitivity (1.0), it takes about ~100 scrolls from min to max + var camSpeedDelta = _input.MouseWheelDelta * options.Viewport.MouseWheelSensitivity * 0.01f; + AdjustCameraMoveSpeed(camSpeedDelta); } } @@ -1686,22 +1715,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; From 4b78d5e39e1be9b8cba2a4c857ce9ed7b831ec84 Mon Sep 17 00:00:00 2001 From: Christopher Rothert Date: Wed, 4 Oct 2023 23:29:32 +0200 Subject: [PATCH 07/78] Orthographic/perspective mode toggle Note: missing dedicated icon! --- Source/Editor/Viewport/EditorViewport.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Source/Editor/Viewport/EditorViewport.cs b/Source/Editor/Viewport/EditorViewport.cs index 51b3a2d78..f83d9eb98 100644 --- a/Source/Editor/Viewport/EditorViewport.cs +++ b/Source/Editor/Viewport/EditorViewport.cs @@ -134,6 +134,11 @@ namespace FlaxEditor.Viewport ///
protected ViewportWidgetButton _cameraButton; + /// + /// The orthographic mode widget button. + /// + protected ViewportWidgetButton _orthographicModeButton; + private readonly Editor _editor; private float _mouseSensitivity; @@ -524,6 +529,15 @@ namespace FlaxEditor.Viewport }; _cameraWidget.Parent = this; + // Toggle orthographic 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; @@ -942,6 +956,9 @@ namespace FlaxEditor.Viewport { _isOrtho = !_isOrtho; + if (_orthographicModeButton != null) + _orthographicModeButton.Checked = !_isOrtho; + if (_isOrtho) { var orient = ViewOrientation; From 3a9fdd8b52dba06013b6a1fe0eee4887f898810b Mon Sep 17 00:00:00 2001 From: Christopher Rothert Date: Thu, 5 Oct 2023 12:55:00 +0200 Subject: [PATCH 08/78] Add possibility to disable camera easing * add camera easing setting to camera settings widget * add camera easing default setting to ViewportOptions * update some settings in ViewportOptions --- Source/Editor/Options/ViewportOptions.cs | 13 ++++-- Source/Editor/Viewport/EditorViewport.cs | 59 +++++++++++++++++++++--- 2 files changed, 63 insertions(+), 9 deletions(-) diff --git a/Source/Editor/Options/ViewportOptions.cs b/Source/Editor/Options/ViewportOptions.cs index 26caa2694..c934fc348 100644 --- a/Source/Editor/Options/ViewportOptions.cs +++ b/Source/Editor/Options/ViewportOptions.cs @@ -46,11 +46,18 @@ namespace FlaxEditor.Options [EditorDisplay("Defaults"), EditorOrder(112), Tooltip("The default maximum movement speed for the viewport camera.")] public float MaxMovementSpeed { get; set; } = 64f; + /// + /// Gets or sets the default camera easing mode. + /// + [DefaultValue(true)] + [EditorDisplay("Defaults"), EditorOrder(120), Tooltip("The default camera easing mode.")] + public bool UseCameraEasing { get; set; } = true; + /// /// Gets or sets the degree to which the camera will be eased when using camera flight in the editor window. /// [DefaultValue(3.0f), Limit(1.0f, 8.0f)] - [EditorDisplay("Defaults"), EditorOrder(120), Tooltip("The default degree to which the camera will be eased when using camera flight in the editor window.")] + [EditorDisplay("Defaults"), EditorOrder(121), Tooltip("The default 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; /// @@ -79,7 +86,7 @@ namespace FlaxEditor.Options /// [DefaultValue(false)] [EditorDisplay("Defaults"), EditorOrder(160), Tooltip("The default camera orthographic mode.")] - public bool OrthographicProjection { get; set; } = false; + public bool UseOrthographicProjection { get; set; } = false; /// /// Gets or sets the default camera orthographic scale (if camera uses orthographic mode). @@ -100,7 +107,7 @@ namespace FlaxEditor.Options /// [DefaultValue(true)] [EditorDisplay("Defaults"), EditorOrder(190), Tooltip("The default relative panning mode. Uses distance between camera and target to determine panning speed.")] - public bool RelativePanning { get; set; } = true; + public bool UseRelativePanning { get; set; } = true; /// /// Gets or sets the default panning speed (ignored if relative panning is speed enabled). diff --git a/Source/Editor/Viewport/EditorViewport.cs b/Source/Editor/Viewport/EditorViewport.cs index f83d9eb98..de4311452 100644 --- a/Source/Editor/Viewport/EditorViewport.cs +++ b/Source/Editor/Viewport/EditorViewport.cs @@ -187,13 +187,14 @@ namespace FlaxEditor.Viewport private float _farPlane; private float _orthoSize; private bool _isOrtho; + private bool _useCameraEasing; private float _cameraEasingDegree; private float _panningSpeed; private bool _relativePanning; private bool _invertPanning; private float _linearMovementProgress; - private float _easedMovementProgress; + private float _easedMovementProgress = 0.0f; /// /// Speed of the mouse. @@ -238,6 +239,15 @@ namespace FlaxEditor.Viewport set => _maxMovementSpeed = value; } + /// + /// Gets or sets the camera easing mode. + /// + public bool UseCameraEasing + { + get => _useCameraEasing; + set => _useCameraEasing = value; + } + /// /// Gets the mouse movement position delta (user press and move). /// @@ -490,6 +500,8 @@ namespace FlaxEditor.Viewport _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)) @@ -584,6 +596,19 @@ namespace FlaxEditor.Viewport }; 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"); @@ -875,11 +900,12 @@ namespace FlaxEditor.Viewport _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.RelativePanning; + _relativePanning = options.Viewport.UseRelativePanning; - _isOrtho = options.Viewport.OrthographicProjection; + _isOrtho = options.Viewport.UseOrthographicProjection; _orthoSize = options.Viewport.OrthographicScale; _fieldOfView = options.Viewport.FieldOfView; _nearPlane = options.Viewport.NearPlane; @@ -920,6 +946,14 @@ namespace FlaxEditor.Viewport 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) { @@ -1056,6 +1090,8 @@ namespace FlaxEditor.Viewport ? 0.0f : Mathf.Remap(_movementSpeed, _minMovementSpeed, _maxMovementSpeed, 0.0f, 1.0f); + if (!_useCameraEasing) + return; _easedMovementProgress = InterpInverseEaseInOut(0.0f, 1.0f, _linearMovementProgress, _cameraEasingDegree); } @@ -1065,9 +1101,20 @@ namespace FlaxEditor.Viewport /// The difference in camera speed adjustment as a fraction of 1. protected void AdjustCameraMoveSpeed(float speedDelta) { - _easedMovementProgress = Mathf.Clamp(_easedMovementProgress + speedDelta, 0.0f, 1.0f); - var easedSpeed = Mathf.InterpEaseInOut(_minMovementSpeed, _maxMovementSpeed, _easedMovementProgress, _cameraEasingDegree); - MovementSpeed = Mathf.Round(easedSpeed * 100) / 100; + float speed; + + if (_useCameraEasing) + { + _easedMovementProgress = Mathf.Clamp(_easedMovementProgress + speedDelta, 0.0f, 1.0f); + speed = Mathf.InterpEaseInOut(_minMovementSpeed, _maxMovementSpeed, _easedMovementProgress, _cameraEasingDegree); + } + else + { + _linearMovementProgress = Mathf.Clamp(_linearMovementProgress + speedDelta, 0.0f, 1.0f); + speed = Mathf.Lerp(_minMovementSpeed, _maxMovementSpeed, _linearMovementProgress); + } + + MovementSpeed = Mathf.Round(speed * 100) / 100; } private void OnEditorOptionsChanged(EditorOptions options) From fd94cfb4690aea20cb0f0c4c0e5db139da451c4c Mon Sep 17 00:00:00 2001 From: Christopher Rothert Date: Thu, 5 Oct 2023 20:04:09 +0200 Subject: [PATCH 09/78] Update how camera settings widget is displayed * add option to force a certain text width to ViewportWidgetButton * force camera settings widget to fit longest possible speed value * changed displayed camera speed using string.Format * changed some lines to always correctly display the camera speed * switched from Mathf.Clamp to Mathf.Saturate for values between 0 and 1 --- Source/Editor/Viewport/EditorViewport.cs | 26 +++++++++---------- .../Viewport/Widgets/ViewportWidgetButton.cs | 11 +++++--- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/Source/Editor/Viewport/EditorViewport.cs b/Source/Editor/Viewport/EditorViewport.cs index de4311452..399a71418 100644 --- a/Source/Editor/Viewport/EditorViewport.cs +++ b/Source/Editor/Viewport/EditorViewport.cs @@ -217,7 +217,7 @@ namespace FlaxEditor.Viewport _movementSpeed = value; if (_cameraButton != null) - _cameraButton.Text = _movementSpeed.ToString(); + _cameraButton.Text = string.Format("{0:0.##}", _movementSpeed); } } @@ -495,7 +495,7 @@ namespace FlaxEditor.Viewport // Initialize camera values from cache if (_editor.ProjectCache.TryGetCustomData("CameraMovementSpeedValue", out var cachedState)) - _movementSpeed = float.Parse(cachedState); + MovementSpeed = float.Parse(cachedState); if (_editor.ProjectCache.TryGetCustomData("CameraMinMovementSpeedValue", out cachedState)) _minMovementSpeed = float.Parse(cachedState); if (_editor.ProjectCache.TryGetCustomData("CameraMaxMovementSpeedValue", out cachedState)) @@ -533,7 +533,7 @@ namespace FlaxEditor.Viewport // Camera settings menu var cameraCM = new ContextMenu(); - _cameraButton = new ViewportWidgetButton(_movementSpeed.ToString(), Editor.Instance.Icons.Camera64, cameraCM) + _cameraButton = new ViewportWidgetButton(string.Format("{0:0.##}", _movementSpeed), Editor.Instance.Icons.Camera64, cameraCM, false, Style.Current.FontMedium.MeasureText("000.00").X) { Tag = this, TooltipText = "Camera Settings", @@ -546,7 +546,7 @@ namespace FlaxEditor.Viewport { Checked = !_isOrtho, TooltipText = "Toggle Orthographic/Perspective Mode", - Parent = _cameraWidget, + Parent = _cameraWidget }; _orthographicModeButton.Toggled += OnOrthographicModeToggled; @@ -555,7 +555,7 @@ namespace FlaxEditor.Viewport camSpeedButton.CloseMenuOnClick = false; var camSpeedValue = new FloatValueBox(_movementSpeed, xLocationForExtras, 2, 70.0f, _minMovementSpeed, _maxMovementSpeed, 0.5f) { - Parent = camSpeedButton, + Parent = camSpeedButton }; camSpeedValue.ValueChanged += () => OnMovementSpeedChanged(camSpeedValue); @@ -566,13 +566,13 @@ namespace FlaxEditor.Viewport minCamSpeedButton.CloseMenuOnClick = false; var minCamSpeedValue = new FloatValueBox(_minMovementSpeed, xLocationForExtras, 2, 70.0f, 0.05f, _maxMovementSpeed, 0.5f) { - Parent = minCamSpeedButton, + 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, + Parent = maxCamSpeedButton }; minCamSpeedValue.ValueChanged += () => @@ -898,7 +898,7 @@ namespace FlaxEditor.Viewport { var options = Editor.Instance.Options.Options; _minMovementSpeed = options.Viewport.MinMovementSpeed; - _movementSpeed = options.Viewport.MovementSpeed; + MovementSpeed = options.Viewport.MovementSpeed; _maxMovementSpeed = options.Viewport.MaxMovementSpeed; _useCameraEasing = options.Viewport.UseCameraEasing; _panningSpeed = options.Viewport.PanningSpeed; @@ -929,7 +929,7 @@ namespace FlaxEditor.Viewport _minMovementSpeed = value; if (_movementSpeed < value) - _movementSpeed = value; + MovementSpeed = value; OnCameraMovementProgressChanged(); _editor.ProjectCache.SetCustomData("CameraMinMovementSpeedValue", _minMovementSpeed.ToString()); @@ -941,7 +941,7 @@ namespace FlaxEditor.Viewport _maxMovementSpeed = value; if (_movementSpeed > value) - _movementSpeed = value; + MovementSpeed = value; OnCameraMovementProgressChanged(); _editor.ProjectCache.SetCustomData("CameraMaxMovementSpeedValue", _maxMovementSpeed.ToString()); @@ -1105,16 +1105,16 @@ namespace FlaxEditor.Viewport if (_useCameraEasing) { - _easedMovementProgress = Mathf.Clamp(_easedMovementProgress + speedDelta, 0.0f, 1.0f); + _easedMovementProgress = Mathf.Saturate(_easedMovementProgress + speedDelta); speed = Mathf.InterpEaseInOut(_minMovementSpeed, _maxMovementSpeed, _easedMovementProgress, _cameraEasingDegree); } else { - _linearMovementProgress = Mathf.Clamp(_linearMovementProgress + speedDelta, 0.0f, 1.0f); + _linearMovementProgress = Mathf.Saturate(_linearMovementProgress + speedDelta); speed = Mathf.Lerp(_minMovementSpeed, _maxMovementSpeed, _linearMovementProgress); } - MovementSpeed = Mathf.Round(speed * 100) / 100; + MovementSpeed = speed; } private void OnEditorOptionsChanged(EditorOptions options) diff --git a/Source/Editor/Viewport/Widgets/ViewportWidgetButton.cs b/Source/Editor/Viewport/Widgets/ViewportWidgetButton.cs index 481bc3f1b..5ff5cdb6d 100644 --- a/Source/Editor/Viewport/Widgets/ViewportWidgetButton.cs +++ b/Source/Editor/Viewport/Widgets/ViewportWidgetButton.cs @@ -19,6 +19,7 @@ namespace FlaxEditor.Viewport.Widgets private bool _checked; private bool _autoCheck; private bool _isMosueDown; + private float _forcedTextWidth; /// /// Event fired when user toggles checked state. @@ -63,14 +64,16 @@ namespace FlaxEditor.Viewport.Widgets /// The text. /// The icon. /// The context menu. - /// if set to true will be automatic checked on mouse click. - public ViewportWidgetButton(string text, SpriteHandle icon, ContextMenu contextMenu = null, bool autoCheck = false) - : base(0, 0, CalculateButtonWidth(0, icon.IsValid), ViewportWidgetsContainer.WidgetsHeight) + /// If set to true will be automatic checked on mouse click. + /// Forces the text to be drawn with the specified width. + public ViewportWidgetButton(string text, SpriteHandle icon, ContextMenu contextMenu = null, bool autoCheck = false, float textWidth = 0.0f) + : base(0, 0, CalculateButtonWidth(textWidth, icon.IsValid), ViewportWidgetsContainer.WidgetsHeight) { _text = text; Icon = icon; _cm = contextMenu; _autoCheck = autoCheck; + _forcedTextWidth = textWidth; if (_cm != null) _cm.VisibleChanged += CmOnVisibleChanged; @@ -160,7 +163,7 @@ namespace FlaxEditor.Viewport.Widgets var style = Style.Current; if (style != null && style.FontMedium) - Width = CalculateButtonWidth(style.FontMedium.MeasureText(_text).X, Icon.IsValid); + Width = CalculateButtonWidth(_forcedTextWidth > 0.0f ? _forcedTextWidth : style.FontMedium.MeasureText(_text).X, Icon.IsValid); } } } From 00aa54cde82da867b2a861cd1b6bf307dafc0a7f Mon Sep 17 00:00:00 2001 From: Christopher Rothert Date: Sat, 7 Oct 2023 02:40:52 +0200 Subject: [PATCH 10/78] Update camera speed stepping and easing * remove complex curve function * update camera easing feature to use Mathf.Pow instead * add total camera speed steps to ViewportOptions * change default value for max camera speed from 64 to 32 * update ViewportOptions ordering and grouping * update string format for movement speed --- Source/Editor/Options/ViewportOptions.cs | 77 +++++++++++--------- Source/Editor/Viewport/EditorViewport.cs | 93 ++++++++---------------- 2 files changed, 72 insertions(+), 98 deletions(-) diff --git a/Source/Editor/Options/ViewportOptions.cs b/Source/Editor/Options/ViewportOptions.cs index c934fc348..0fd018cbc 100644 --- a/Source/Editor/Options/ViewportOptions.cs +++ b/Source/Editor/Options/ViewportOptions.cs @@ -26,101 +26,108 @@ namespace FlaxEditor.Options public float MouseWheelSensitivity { get; set; } = 1.0f; /// - /// Gets or sets the default movement speed for the viewport camera (must be in range between minimum and maximum movement speed values). + /// Gets or sets the total amount of steps the camera needs to go from minimum to maximum speed. /// - [DefaultValue(1.0f), Limit(0.05f, 64.0f)] - [EditorDisplay("Defaults"), EditorOrder(110), Tooltip("The default movement speed for the viewport camera (must be in range between minimum and maximum movement speed values).")] - public float MovementSpeed { get; set; } = 1.0f; - - /// - /// Gets or sets the default minimum camera movement speed. - /// - [DefaultValue(0.05f), Limit(0.05f, 64.0f)] - [EditorDisplay("Defaults"), EditorOrder(111), Tooltip("The default minimum movement speed for the viewport camera.")] - public float MinMovementSpeed { get; set; } = 0.05f; - - /// - /// Gets or sets the default maximum camera movement speed. - /// - [DefaultValue(64.0f), Limit(32.0f, 1000.0f)] - [EditorDisplay("Defaults"), EditorOrder(112), Tooltip("The default maximum movement speed for the viewport camera.")] - public float MaxMovementSpeed { get; set; } = 64f; - - /// - /// Gets or sets the default camera easing mode. - /// - [DefaultValue(true)] - [EditorDisplay("Defaults"), EditorOrder(120), Tooltip("The default camera easing mode.")] - public bool UseCameraEasing { get; set; } = true; + [DefaultValue(64), Limit(1, 128)] + [EditorDisplay("Camera"), EditorOrder(110), Tooltip("The total amount of steps the camera needs to go from minimum to maximum speed.")] + public int TotalCameraSpeedSteps { get; set; } = 64; /// /// Gets or sets the degree to which the camera will be eased when using camera flight in the editor window. /// [DefaultValue(3.0f), Limit(1.0f, 8.0f)] - [EditorDisplay("Defaults"), EditorOrder(121), Tooltip("The default degree to which the camera will be eased when using camera flight in the editor window (ignored if camera easing degree is enabled).")] + [EditorDisplay("Camera"), EditorOrder(111), Tooltip("The degree to which the camera will be eased when using camera flight in the editor window (ignored if camera easing degree is enabled).")] public float CameraEasingDegree { get; set; } = 3.0f; + /// + /// Gets or sets the default movement speed for the viewport camera (must be in range between minimum and maximum movement speed values). + /// + [DefaultValue(1.0f), Limit(0.05f, 32.0f)] + [EditorDisplay("Defaults"), EditorOrder(120), Tooltip("The default movement speed for the viewport camera (must be in range between minimum and maximum movement speed values).")] + public float MovementSpeed { get; set; } = 1.0f; + + /// + /// Gets or sets the default minimum camera movement speed. + /// + [DefaultValue(0.05f), Limit(0.05f, 32.0f)] + [EditorDisplay("Defaults"), EditorOrder(121), Tooltip("The default minimum movement speed for the viewport camera.")] + public float MinMovementSpeed { get; set; } = 0.05f; + + /// + /// Gets or sets the default maximum camera movement speed. + /// + [DefaultValue(32.0f), Limit(16.0f, 1000.0f)] + [EditorDisplay("Defaults"), EditorOrder(122), Tooltip("The default maximum movement speed for the viewport camera.")] + public float MaxMovementSpeed { get; set; } = 32f; + + /// + /// Gets or sets the default camera easing mode. + /// + [DefaultValue(true)] + [EditorDisplay("Defaults"), EditorOrder(130), Tooltip("The default camera easing mode.")] + public bool UseCameraEasing { get; set; } = true; + /// /// Gets or sets the default near clipping plane distance for the viewport camera. /// [DefaultValue(10.0f), Limit(0.001f, 1000.0f)] - [EditorDisplay("Defaults"), EditorOrder(130), Tooltip("The default near clipping plane distance for the viewport camera.")] + [EditorDisplay("Defaults"), EditorOrder(140), Tooltip("The default near clipping plane distance for the viewport camera.")] public float NearPlane { get; set; } = 10.0f; /// /// Gets or sets the default far clipping plane distance for the viewport camera. /// [DefaultValue(40000.0f), Limit(10.0f)] - [EditorDisplay("Defaults"), EditorOrder(140), Tooltip("The default far clipping plane distance for the viewport camera.")] + [EditorDisplay("Defaults"), EditorOrder(150), Tooltip("The default far clipping plane distance for the viewport camera.")] public float FarPlane { get; set; } = 40000.0f; /// /// Gets or sets the default field of view angle (in degrees) for the viewport camera. /// [DefaultValue(60.0f), Limit(35.0f, 160.0f, 0.1f)] - [EditorDisplay("Defaults"), EditorOrder(150), Tooltip("The default field of view angle (in degrees) for the viewport camera.")] + [EditorDisplay("Defaults"), EditorOrder(160), Tooltip("The default field of view angle (in degrees) for the viewport camera.")] public float FieldOfView { get; set; } = 60.0f; /// /// Gets or sets the default camera orthographic mode. /// [DefaultValue(false)] - [EditorDisplay("Defaults"), EditorOrder(160), Tooltip("The default camera orthographic mode.")] + [EditorDisplay("Defaults"), EditorOrder(170), Tooltip("The default camera orthographic mode.")] public bool UseOrthographicProjection { get; set; } = false; /// /// Gets or sets the default camera orthographic scale (if camera uses orthographic mode). /// [DefaultValue(5.0f), Limit(0.001f, 100000.0f, 0.1f)] - [EditorDisplay("Defaults"), EditorOrder(170), Tooltip("The default camera orthographic scale (if camera uses orthographic mode).")] + [EditorDisplay("Defaults"), EditorOrder(180), Tooltip("The default camera orthographic scale (if camera uses orthographic mode).")] public float OrthographicScale { get; set; } = 5.0f; /// /// Gets or sets the default panning direction for the viewport camera. /// [DefaultValue(false)] - [EditorDisplay("Defaults"), EditorOrder(180), Tooltip("The default panning direction for the viewport camera.")] + [EditorDisplay("Defaults"), EditorOrder(190), Tooltip("The default panning direction for the viewport camera.")] public bool InvertPanning { get; set; } = false; /// /// Gets or sets the default relative panning mode. /// [DefaultValue(true)] - [EditorDisplay("Defaults"), EditorOrder(190), Tooltip("The default relative panning mode. Uses distance between camera and target to determine panning speed.")] + [EditorDisplay("Defaults"), EditorOrder(200), Tooltip("The default relative panning mode. Uses distance between camera and target to determine panning speed.")] public bool UseRelativePanning { get; set; } = true; /// /// Gets or sets the default panning speed (ignored if relative panning is speed enabled). /// [DefaultValue(0.8f), Limit(0.01f, 128.0f, 0.1f)] - [EditorDisplay("Defaults"), EditorOrder(200), Tooltip("The default camera panning speed (ignored if relative panning is enabled).")] + [EditorDisplay("Defaults"), EditorOrder(210), Tooltip("The default camera panning speed (ignored if relative panning is enabled).")] public float PanningSpeed { get; set; } = 0.8f; /// /// Gets or sets the default editor viewport grid scale. /// [DefaultValue(50.0f), Limit(25.0f, 500.0f, 5.0f)] - [EditorDisplay("Defaults"), EditorOrder(210), Tooltip("The default editor viewport grid scale.")] + [EditorDisplay("Defaults"), EditorOrder(220), Tooltip("The default editor viewport grid scale.")] public float ViewportGridScale { get; set; } = 50.0f; } } diff --git a/Source/Editor/Viewport/EditorViewport.cs b/Source/Editor/Viewport/EditorViewport.cs index 399a71418..ba4087025 100644 --- a/Source/Editor/Viewport/EditorViewport.cs +++ b/Source/Editor/Viewport/EditorViewport.cs @@ -178,7 +178,6 @@ namespace FlaxEditor.Viewport protected Float2 _mouseDelta; // Camera - private ViewportCamera _camera; private float _yaw; private float _pitch; @@ -193,8 +192,8 @@ namespace FlaxEditor.Viewport private bool _relativePanning; private bool _invertPanning; - private float _linearMovementProgress; - private float _easedMovementProgress = 0.0f; + private int _speedStep; + private int _maxSpeedSteps; /// /// Speed of the mouse. @@ -216,8 +215,9 @@ namespace FlaxEditor.Viewport { _movementSpeed = value; + var format = (_movementSpeed < 1.0f) ? "{0:0.##}" : "{0:#}"; if (_cameraButton != null) - _cameraButton.Text = string.Format("{0:0.##}", _movementSpeed); + _cameraButton.Text = string.Format(format, _movementSpeed); } } @@ -527,13 +527,14 @@ namespace FlaxEditor.Viewport var largestText = "Relative Panning"; var textSize = Style.Current.FontMedium.MeasureText(largestText); var xLocationForExtras = textSize.X + 5; + var format = (_movementSpeed < 1.0f) ? "{0:0.##}" : "{0:#}"; // Camera settings widget _cameraWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight); // Camera settings menu var cameraCM = new ContextMenu(); - _cameraButton = new ViewportWidgetButton(string.Format("{0:0.##}", _movementSpeed), Editor.Instance.Icons.Camera64, cameraCM, false, Style.Current.FontMedium.MeasureText("000.00").X) + _cameraButton = new ViewportWidgetButton(string.Format(format, _movementSpeed), Editor.Instance.Icons.Camera64, cameraCM, false, Style.Current.FontMedium.MeasureText("0.00").X) { Tag = this, TooltipText = "Camera Settings", @@ -884,8 +885,8 @@ namespace FlaxEditor.Viewport InputActions.Add(options => options.ViewpointRight, () => OrientViewport(Quaternion.Euler(EditorViewportCameraViewpointValues.First(vp => vp.Name == "Right").Orientation))); InputActions.Add(options => options.ViewpointLeft, () => OrientViewport(Quaternion.Euler(EditorViewportCameraViewpointValues.First(vp => vp.Name == "Left").Orientation))); InputActions.Add(options => options.CameraToggleRotation, () => _isVirtualMouseRightDown = !_isVirtualMouseRightDown); - InputActions.Add(options => options.CameraIncreaseMoveSpeed, () => AdjustCameraMoveSpeed(Editor.Instance.Options.Options.Viewport.MouseWheelSensitivity * 0.01f)); - InputActions.Add(options => options.CameraDecreaseMoveSpeed, () => AdjustCameraMoveSpeed(Editor.Instance.Options.Options.Viewport.MouseWheelSensitivity * -0.01f)); + InputActions.Add(options => options.CameraIncreaseMoveSpeed, () => AdjustCameraMoveSpeed(1)); + InputActions.Add(options => options.CameraDecreaseMoveSpeed, () => AdjustCameraMoveSpeed(-1)); // Link for task event task.Begin += OnRenderBegin; @@ -1052,74 +1053,42 @@ namespace FlaxEditor.Viewport } } - /// - /// The inverse of .
- /// Interpolate between A and B, applying a reverse ease in function. Exponent controls the degree of the curve. - ///
- private float InterpInverseEaseIn(float a, float b, float alpha, float exponent) - { - return 0.5f * Mathf.Pow((2.0f * (alpha - a) / (b - a)), 1.0f / exponent); - } - - /// - /// The inverse of .
- /// Interpolate between A and B, applying a reverse ease out function. Exponent controls the degree of the curve. - ///
- private float InterpInverseEaseOut(float a, float b, float alpha, float exponent) - { - return 1.0f - InterpInverseEaseIn(a, b, 1.0f - alpha, exponent); - } - - /// - /// The inverse of .
- /// Interpolate between A and B, applying a reverse ease in/out function. Exponent controls the degree of the curve. - ///
- private float InterpInverseEaseInOut(float a, float b, float alpha, float exponent) - { - if (alpha <= 0.0f) - return a; - if (alpha >= 1.0f) - return b; - - return (alpha < 0.5f) ? InterpInverseEaseIn(a, b, alpha, exponent) : InterpInverseEaseOut(a, b, alpha, exponent); - } - private void OnCameraMovementProgressChanged() { - _linearMovementProgress = Math.Abs(_minMovementSpeed - _maxMovementSpeed) < Mathf.Epsilon - ? 0.0f - : Mathf.Remap(_movementSpeed, _minMovementSpeed, _maxMovementSpeed, 0.0f, 1.0f); - - if (!_useCameraEasing) + // prevent NaN + if (Math.Abs(_minMovementSpeed - _maxMovementSpeed) < Mathf.Epsilon) { + _speedStep = 0; return; - _easedMovementProgress = InterpInverseEaseInOut(0.0f, 1.0f, _linearMovementProgress, _cameraEasingDegree); + } + + // calculate current linear/eased progress + float progress = Mathf.Remap(_movementSpeed, _minMovementSpeed, _maxMovementSpeed, 0.0f, 1.0f); + if (_useCameraEasing) + progress = Mathf.Pow(progress, 1.0f / _cameraEasingDegree); + + _speedStep = Mathf.RoundToInt(progress * _maxSpeedSteps); } /// /// Increases or decreases the camera movement speed. /// - /// The difference in camera speed adjustment as a fraction of 1. - protected void AdjustCameraMoveSpeed(float speedDelta) + /// The stepping direction for speed adjustment. + protected void AdjustCameraMoveSpeed(int step) { - float speed; + _speedStep = Mathf.Clamp(_speedStep + step, 0, _maxSpeedSteps); - if (_useCameraEasing) - { - _easedMovementProgress = Mathf.Saturate(_easedMovementProgress + speedDelta); - speed = Mathf.InterpEaseInOut(_minMovementSpeed, _maxMovementSpeed, _easedMovementProgress, _cameraEasingDegree); - } - else - { - _linearMovementProgress = Mathf.Saturate(_linearMovementProgress + speedDelta); - speed = Mathf.Lerp(_minMovementSpeed, _maxMovementSpeed, _linearMovementProgress); - } + // calculate new linear/eased progress + var progress = _useCameraEasing + ? Mathf.Pow((float)_speedStep / _maxSpeedSteps, _cameraEasingDegree) + : (float)_speedStep / _maxSpeedSteps; - MovementSpeed = speed; + MovementSpeed = Mathf.Lerp(_minMovementSpeed, _maxMovementSpeed, progress); } private void OnEditorOptionsChanged(EditorOptions options) { _mouseSensitivity = options.Viewport.MouseSensitivity; + _maxSpeedSteps = options.Viewport.TotalCameraSpeedSteps; _cameraEasingDegree = options.Viewport.CameraEasingDegree; OnCameraMovementProgressChanged(); } @@ -1507,10 +1476,8 @@ namespace FlaxEditor.Viewport rmbWheel = useMovementSpeed && (_input.IsMouseRightDown || _isVirtualMouseRightDown) && wheelInUse; if (rmbWheel) { - // speed delta can be adjusted through mouse wheel sensitivity in editor - // with default sensitivity (1.0), it takes about ~100 scrolls from min to max - var camSpeedDelta = _input.MouseWheelDelta * options.Viewport.MouseWheelSensitivity * 0.01f; - AdjustCameraMoveSpeed(camSpeedDelta); + var step = _input.MouseWheelDelta * options.Viewport.MouseWheelSensitivity; + AdjustCameraMoveSpeed(step > 0.0f ? 1 : -1); } } From 997c442e69366cc1c5e4404368fd252a4d134bf7 Mon Sep 17 00:00:00 2001 From: Christopher Rothert Date: Sun, 8 Oct 2023 01:07:36 +0200 Subject: [PATCH 11/78] Update movement speed formatting * add property for movement speed text format * add additional checks to camera speed progress calculation * round movement speed to make it visually more appealing in the context menu --- Source/Editor/Viewport/EditorViewport.cs | 45 ++++++++++++++++++++---- 1 file changed, 38 insertions(+), 7 deletions(-) diff --git a/Source/Editor/Viewport/EditorViewport.cs b/Source/Editor/Viewport/EditorViewport.cs index ba4087025..8ce99b115 100644 --- a/Source/Editor/Viewport/EditorViewport.cs +++ b/Source/Editor/Viewport/EditorViewport.cs @@ -178,6 +178,7 @@ namespace FlaxEditor.Viewport protected Float2 _mouseDelta; // Camera + private ViewportCamera _camera; private float _yaw; private float _pitch; @@ -205,6 +206,23 @@ namespace FlaxEditor.Viewport ///
public float MouseWheelZoomSpeedFactor = 1; + /// + /// Format of the text for the camera move speed. + /// + private string MovementSpeedTextFormat { + get { + if (Mathf.Abs(_movementSpeed - _maxMovementSpeed) < Mathf.Epsilon || Mathf.Abs(_movementSpeed - _minMovementSpeed) < Mathf.Epsilon) + return "{0:0.##}"; + + if (_movementSpeed < 10.0f) + return "{0:0.00}"; + else if (_movementSpeed < 100.0f) + return "{0:0.0}"; + else + return "{0:#}"; + } + } + /// /// Gets or sets the camera movement speed. /// @@ -215,9 +233,8 @@ namespace FlaxEditor.Viewport { _movementSpeed = value; - var format = (_movementSpeed < 1.0f) ? "{0:0.##}" : "{0:#}"; if (_cameraButton != null) - _cameraButton.Text = string.Format(format, _movementSpeed); + _cameraButton.Text = string.Format(MovementSpeedTextFormat, _movementSpeed); } } @@ -527,14 +544,14 @@ namespace FlaxEditor.Viewport var largestText = "Relative Panning"; var textSize = Style.Current.FontMedium.MeasureText(largestText); var xLocationForExtras = textSize.X + 5; - var format = (_movementSpeed < 1.0f) ? "{0:0.##}" : "{0:#}"; + 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(format, _movementSpeed), Editor.Instance.Icons.Camera64, cameraCM, false, Style.Current.FontMedium.MeasureText("0.00").X) + _cameraButton = new ViewportWidgetButton(string.Format(MovementSpeedTextFormat, _movementSpeed), Editor.Instance.Icons.Camera64, cameraCM, false, cameraSpeedTextWidth) { Tag = this, TooltipText = "Camera Settings", @@ -1060,9 +1077,21 @@ namespace FlaxEditor.Viewport _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 - float progress = Mathf.Remap(_movementSpeed, _minMovementSpeed, _maxMovementSpeed, 0.0f, 1.0f); + var progress = Mathf.Remap(_movementSpeed, _minMovementSpeed, _maxMovementSpeed, 0.0f, 1.0f); + if (_useCameraEasing) progress = Mathf.Pow(progress, 1.0f / _cameraEasingDegree); @@ -1081,8 +1110,10 @@ namespace FlaxEditor.Viewport var progress = _useCameraEasing ? Mathf.Pow((float)_speedStep / _maxSpeedSteps, _cameraEasingDegree) : (float)_speedStep / _maxSpeedSteps; - - MovementSpeed = Mathf.Lerp(_minMovementSpeed, _maxMovementSpeed, progress); + + var speed = Mathf.Lerp(_minMovementSpeed, _maxMovementSpeed, progress); + MovementSpeed = (float)Math.Round(speed, 3); + _editor.ProjectCache.SetCustomData("CameraMovementSpeedValue", _movementSpeed.ToString()); } private void OnEditorOptionsChanged(EditorOptions options) From 966e5cdda27b209d2a775375b21379cb72b4fc3e Mon Sep 17 00:00:00 2001 From: Christopher Rothert Date: Sun, 8 Oct 2023 01:30:35 +0200 Subject: [PATCH 12/78] Update some comments --- Source/Editor/Viewport/EditorViewport.cs | 40 +++++++++++++----------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/Source/Editor/Viewport/EditorViewport.cs b/Source/Editor/Viewport/EditorViewport.cs index 8ce99b115..8f733ce0c 100644 --- a/Source/Editor/Viewport/EditorViewport.cs +++ b/Source/Editor/Viewport/EditorViewport.cs @@ -546,10 +546,10 @@ namespace FlaxEditor.Viewport var xLocationForExtras = textSize.X + 5; var cameraSpeedTextWidth = Style.Current.FontMedium.MeasureText("0.00").X; - // Camera settings widget + // Camera Settings Widget _cameraWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight); - // Camera settings menu + // Camera Settings Menu var cameraCM = new ContextMenu(); _cameraButton = new ViewportWidgetButton(string.Format(MovementSpeedTextFormat, _movementSpeed), Editor.Instance.Icons.Camera64, cameraCM, false, cameraSpeedTextWidth) { @@ -559,7 +559,7 @@ namespace FlaxEditor.Viewport }; _cameraWidget.Parent = this; - // Toggle orthographic mode widget + // Orthographic/Perspective Mode Widget _orthographicModeButton = new ViewportWidgetButton(string.Empty, Editor.Instance.Icons.CamSpeed32, null, true) { Checked = !_isOrtho, @@ -568,7 +568,7 @@ namespace FlaxEditor.Viewport }; _orthographicModeButton.Toggled += OnOrthographicModeToggled; - // Camera speed + // Camera Speed var camSpeedButton = cameraCM.AddButton("Camera Speed"); camSpeedButton.CloseMenuOnClick = false; var camSpeedValue = new FloatValueBox(_movementSpeed, xLocationForExtras, 2, 70.0f, _minMovementSpeed, _maxMovementSpeed, 0.5f) @@ -579,7 +579,7 @@ namespace FlaxEditor.Viewport camSpeedValue.ValueChanged += () => OnMovementSpeedChanged(camSpeedValue); cameraCM.VisibleChanged += control => camSpeedValue.Value = _movementSpeed; - // Minimum & maximum camera speed + // 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) @@ -614,7 +614,7 @@ namespace FlaxEditor.Viewport }; cameraCM.VisibleChanged += control => maxCamSpeedValue.Value = _maxMovementSpeed; - // Camera easing + // Camera Easing { var useCameraEasing = cameraCM.AddButton("Camera Easing"); useCameraEasing.CloseMenuOnClick = false; @@ -627,7 +627,7 @@ namespace FlaxEditor.Viewport cameraCM.VisibleChanged += control => useCameraEasingValue.Checked = _useCameraEasing; } - // Panning speed + // Panning Speed { var panningSpeed = cameraCM.AddButton("Panning Speed"); panningSpeed.CloseMenuOnClick = false; @@ -644,7 +644,7 @@ namespace FlaxEditor.Viewport }; } - // Relative panning + // Relative Panning { var relativePanning = cameraCM.AddButton("Relative Panning"); relativePanning.CloseMenuOnClick = false; @@ -664,7 +664,7 @@ namespace FlaxEditor.Viewport cameraCM.VisibleChanged += control => relativePanningValue.Checked = _relativePanning; } - // Invert panning + // Invert Panning { var invertPanning = cameraCM.AddButton("Invert Panning"); invertPanning.CloseMenuOnClick = false; @@ -679,7 +679,7 @@ namespace FlaxEditor.Viewport cameraCM.AddSeparator(); - // Camera viewpoints + // Camera Viewpoints { var cameraView = cameraCM.AddChildMenu("Viewpoints").ContextMenu; for (int i = 0; i < EditorViewportCameraViewpointValues.Length; i++) @@ -692,7 +692,7 @@ namespace FlaxEditor.Viewport cameraView.ButtonClicked += OnViewpointChanged; } - // Orthographic mode + // Orthographic Mode { var ortho = cameraCM.AddButton("Orthographic"); ortho.CloseMenuOnClick = false; @@ -712,7 +712,7 @@ namespace FlaxEditor.Viewport cameraCM.VisibleChanged += control => orthoValue.Checked = _isOrtho; } - // Field of view + // Field of View { var fov = cameraCM.AddButton("Field Of View"); fov.CloseMenuOnClick = false; @@ -729,7 +729,7 @@ namespace FlaxEditor.Viewport }; } - // Orthographic scale + // Orthographic Scale { var orthoSize = cameraCM.AddButton("Ortho Scale"); orthoSize.CloseMenuOnClick = false; @@ -746,7 +746,7 @@ namespace FlaxEditor.Viewport }; } - // Near plane + // Near Plane { var nearPlane = cameraCM.AddButton("Near Plane"); nearPlane.CloseMenuOnClick = false; @@ -759,7 +759,7 @@ namespace FlaxEditor.Viewport cameraCM.VisibleChanged += control => nearPlaneValue.Value = _nearPlane; } - // Far plane + // Far Plane { var farPlane = cameraCM.AddButton("Far Plane"); farPlane.CloseMenuOnClick = false; @@ -774,14 +774,16 @@ namespace FlaxEditor.Viewport cameraCM.AddSeparator(); - // Reset button + // Reset Button { var reset = cameraCM.AddButton("Reset to default"); reset.ButtonClicked += button => { SetupViewportOptions(); - // trigger UI update + // 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; @@ -815,7 +817,7 @@ namespace FlaxEditor.Viewport } } - // View flags + // View Flags { var viewFlags = ViewWidgetButtonMenu.AddChildMenu("View Flags").ContextMenu; viewFlags.AddButton("Reset flags", () => Task.ViewFlags = ViewFlags.DefaultEditor).Icon = Editor.Instance.Icons.Rotate32; @@ -839,7 +841,7 @@ namespace FlaxEditor.Viewport viewFlags.VisibleChanged += WidgetViewFlagsShowHide; } - // Debug view + // Debug View { var debugView = ViewWidgetButtonMenu.AddChildMenu("Debug View").ContextMenu; for (int i = 0; i < EditorViewportViewModeValues.Length; i++) From 55ad5ae367a094105b2dec06e43433cf70593a1d Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 18 Oct 2023 16:17:19 +0200 Subject: [PATCH 13/78] Build Editor bindings only when generating editor project files #1734 #1569 --- GenerateProjectFiles.bat | 2 +- GenerateProjectFiles.command | 2 +- GenerateProjectFiles.sh | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/GenerateProjectFiles.bat b/GenerateProjectFiles.bat index 622939c34..28970a203 100644 --- a/GenerateProjectFiles.bat +++ b/GenerateProjectFiles.bat @@ -15,7 +15,7 @@ if errorlevel 1 goto BuildToolFailed :: Build bindings for all editor configurations echo Building C# bindings... -Binaries\Tools\Flax.Build.exe -build -BuildBindingsOnly -arch=x64 -platform=Windows --buildTargets=FlaxEditor,FlaxGame +Binaries\Tools\Flax.Build.exe -build -BuildBindingsOnly -arch=x64 -platform=Windows --buildTargets=FlaxEditor popd echo Done! diff --git a/GenerateProjectFiles.command b/GenerateProjectFiles.command index 5ee5c0783..a42121252 100755 --- a/GenerateProjectFiles.command +++ b/GenerateProjectFiles.command @@ -14,4 +14,4 @@ bash ./Development/Scripts/Mac/CallBuildTool.sh --genproject "$@" # Build bindings for all editor configurations echo Building C# bindings... # TODO: Detect the correct architecture here -Binaries/Tools/Flax.Build -build -BuildBindingsOnly -arch=ARM64 -platform=Mac --buildTargets=FlaxEditor,FlaxGame +Binaries/Tools/Flax.Build -build -BuildBindingsOnly -arch=ARM64 -platform=Mac --buildTargets=FlaxEditor diff --git a/GenerateProjectFiles.sh b/GenerateProjectFiles.sh index dceb8abe8..76d96c7ef 100755 --- a/GenerateProjectFiles.sh +++ b/GenerateProjectFiles.sh @@ -14,4 +14,4 @@ bash ./Development/Scripts/Linux/CallBuildTool.sh --genproject "$@" # Build bindings for all editor configurations echo Building C# bindings... # TODO: Detect the correct architecture here -Binaries/Tools/Flax.Build -build -BuildBindingsOnly -arch=x64 -platform=Linux --buildTargets=FlaxEditor,FlaxGame +Binaries/Tools/Flax.Build -build -BuildBindingsOnly -arch=x64 -platform=Linux --buildTargets=FlaxEditor From 5181db8a0e2f057be0051b3d107516c002e58fb7 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 18 Oct 2023 21:51:36 +0200 Subject: [PATCH 14/78] Fix vscode default build task to favor current architecture (eg. arm64 for Editor on macOS) --- .../VisualStudioCode/VisualStudioCodeProjectGenerator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Tools/Flax.Build/Projects/VisualStudioCode/VisualStudioCodeProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/VisualStudioCode/VisualStudioCodeProjectGenerator.cs index a044adc0a..5c4b2dbe0 100644 --- a/Source/Tools/Flax.Build/Projects/VisualStudioCode/VisualStudioCodeProjectGenerator.cs +++ b/Source/Tools/Flax.Build/Projects/VisualStudioCode/VisualStudioCodeProjectGenerator.cs @@ -172,7 +172,7 @@ namespace Flax.Build.Projects.VisualStudioCode json.AddField("label", name); - bool isDefaultTask = defaultTask && configuration.Configuration == TargetConfiguration.Development && configuration.Platform == Platform.BuildPlatform.Target; + bool isDefaultTask = defaultTask && configuration.Configuration == TargetConfiguration.Development && configuration.Platform == Platform.BuildPlatform.Target && configuration.Architecture == Platform.BuildTargetArchitecture; json.BeginObject("group"); { From f373c867a734dfaa36d9dbd627be73966b3938f7 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 18 Oct 2023 21:57:35 +0200 Subject: [PATCH 15/78] Improve Game Cooker label text when platform data is missing --- Source/Editor/Windows/GameCookerWindow.cs | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/Source/Editor/Windows/GameCookerWindow.cs b/Source/Editor/Windows/GameCookerWindow.cs index 5ece067a0..2ef9c05cf 100644 --- a/Source/Editor/Windows/GameCookerWindow.cs +++ b/Source/Editor/Windows/GameCookerWindow.cs @@ -155,29 +155,42 @@ namespace FlaxEditor.Windows public virtual void OnNotAvailableLayout(LayoutElementsContainer layout) { - layout.Label("Missing platform data tools for the target platform.", TextAlignment.Center); + string text = "Missing platform data tools for the target platform."; if (FlaxEditor.Editor.IsOfficialBuild()) { switch (BuildPlatform) { +#if PLATFORM_WINDOWS case BuildPlatform.Windows32: case BuildPlatform.Windows64: case BuildPlatform.UWPx86: case BuildPlatform.UWPx64: case BuildPlatform.LinuxX64: case BuildPlatform.AndroidARM64: - layout.Label("Use Flax Launcher and download the required package.", TextAlignment.Center); + text += "\nUse Flax Launcher and download the required package."; break; +#endif default: - layout.Label("Engine source is required to target this platform.", TextAlignment.Center); + text += "\nEngine source is required to target this platform."; break; } } else { - var label = layout.Label("To target this platform separate engine source package is required.\nTo get access please contact via https://flaxengine.com/contact", TextAlignment.Center); - label.Label.AutoHeight = true; + text += "\nTo target this platform separate engine source package is required."; + switch (BuildPlatform) + { + case BuildPlatform.XboxOne: + case BuildPlatform.XboxScarlett: + case BuildPlatform.PS4: + case BuildPlatform.PS5: + case BuildPlatform.Switch: + text += "\nTo get access please contact via https://flaxengine.com/contact"; + break; + } } + var label = layout.Label(text, TextAlignment.Center); + label.Label.AutoHeight = true; } public virtual void Build() From 6c45141ef9d89b2ef654500d37906d05582cc8cb Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 18 Oct 2023 22:10:46 +0200 Subject: [PATCH 16/78] Add dmg file building for macOS Editor deployment --- Source/Tools/Flax.Build/Deploy/Deployment.Editor.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Source/Tools/Flax.Build/Deploy/Deployment.Editor.cs b/Source/Tools/Flax.Build/Deploy/Deployment.Editor.cs index 91da6d83d..cfb2446c3 100644 --- a/Source/Tools/Flax.Build/Deploy/Deployment.Editor.cs +++ b/Source/Tools/Flax.Build/Deploy/Deployment.Editor.cs @@ -164,6 +164,15 @@ namespace Flax.Deploy var defaultEditorConfig = "Development"; var ediotrBinariesPath = Path.Combine(appContentsPath, "Binaries/Editor/Mac", defaultEditorConfig); Utilities.DirectoryCopy(ediotrBinariesPath, appBinariesPath, true, true); + + // Build a disk image + var dmgPath = Path.Combine(Deployer.PackageOutputPath, "FlaxEditor.dmg"); + 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)); } // Compress From c0fa858dd09ebe01fd06531e877612c9515edd9d Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 19 Oct 2023 14:25:37 +0200 Subject: [PATCH 17/78] Fix warning on missing windows layout file when loading default layout --- Source/Editor/Modules/WindowsModule.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Source/Editor/Modules/WindowsModule.cs b/Source/Editor/Modules/WindowsModule.cs index 1a4f5ce9e..05d72598f 100644 --- a/Source/Editor/Modules/WindowsModule.cs +++ b/Source/Editor/Modules/WindowsModule.cs @@ -237,7 +237,11 @@ namespace FlaxEditor.Modules ///
public void LoadDefaultLayout() { - LoadLayout(StringUtils.CombinePaths(Globals.EngineContentFolder, "Editor/LayoutDefault.xml")); + var path = StringUtils.CombinePaths(Globals.EngineContentFolder, "Editor/LayoutDefault.xml"); + if (File.Exists(path)) + { + LoadLayout(path); + } } /// From 6f60218becca4fb7b3818f057d09ee0f718abc3f Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 19 Oct 2023 15:07:03 +0200 Subject: [PATCH 18/78] Optimize various rendering stages to skip loading shader when effect is disables --- Source/Engine/Renderer/DepthOfFieldPass.cpp | 8 +++----- Source/Engine/Renderer/EyeAdaptationPass.cpp | 7 +------ Source/Engine/Renderer/MotionBlurPass.cpp | 2 +- Source/Engine/Renderer/PostProcessingPass.cpp | 2 +- 4 files changed, 6 insertions(+), 13 deletions(-) diff --git a/Source/Engine/Renderer/DepthOfFieldPass.cpp b/Source/Engine/Renderer/DepthOfFieldPass.cpp index 4c43ccdbf..cd7930f5c 100644 --- a/Source/Engine/Renderer/DepthOfFieldPass.cpp +++ b/Source/Engine/Renderer/DepthOfFieldPass.cpp @@ -202,16 +202,14 @@ GPUTexture* DepthOfFieldPass::getDofBokehShape(DepthOfFieldSettings& dofSettings void DepthOfFieldPass::Render(RenderContext& renderContext, GPUTexture*& frame, GPUTexture*& tmp) { - if (!_platformSupportsDoF || checkIfSkipPass()) + DepthOfFieldSettings& dofSettings = renderContext.List->Settings.DepthOfField; + const bool useDoF = EnumHasAnyFlags(renderContext.View.Flags, ViewFlags::DepthOfField) && dofSettings.Enabled; + if (!useDoF || _platformSupportsDoF || checkIfSkipPass()) return; auto device = GPUDevice::Instance; auto context = device->GetMainContext(); const auto depthBuffer = renderContext.Buffers->DepthBuffer; const auto shader = _shader->GetShader(); - DepthOfFieldSettings& dofSettings = renderContext.List->Settings.DepthOfField; - const bool useDoF = _platformSupportsDoF && EnumHasAnyFlags(renderContext.View.Flags, ViewFlags::DepthOfField) && dofSettings.Enabled; - if (!useDoF) - return; PROFILE_GPU_CPU("Depth Of Field"); context->ResetSR(); diff --git a/Source/Engine/Renderer/EyeAdaptationPass.cpp b/Source/Engine/Renderer/EyeAdaptationPass.cpp index 12b26ea41..3a840ff01 100644 --- a/Source/Engine/Renderer/EyeAdaptationPass.cpp +++ b/Source/Engine/Renderer/EyeAdaptationPass.cpp @@ -35,7 +35,6 @@ PACK_STRUCT(struct EyeAdaptationData { void EyeAdaptationPass::Render(RenderContext& renderContext, GPUTexture* colorBuffer) { - // Cache data auto device = GPUDevice::Instance; auto context = device->GetMainContext(); auto& view = renderContext.View; @@ -45,12 +44,8 @@ void EyeAdaptationPass::Render(RenderContext& renderContext, GPUTexture* colorBu //const float frameDelta = Time::ElapsedGameTime.GetTotalSeconds(); const float frameDelta = time - renderContext.Buffers->LastEyeAdaptationTime; renderContext.Buffers->LastEyeAdaptationTime = 0.0f; - - // Optionally skip the rendering - if (checkIfSkipPass() || (view.Flags & ViewFlags::EyeAdaptation) == ViewFlags::None || settings.Mode == EyeAdaptationMode::None) - { + if ((view.Flags & ViewFlags::EyeAdaptation) == ViewFlags::None || settings.Mode == EyeAdaptationMode::None || checkIfSkipPass()) return; - } PROFILE_GPU_CPU("Eye Adaptation"); diff --git a/Source/Engine/Renderer/MotionBlurPass.cpp b/Source/Engine/Renderer/MotionBlurPass.cpp index d425df80f..2030027ed 100644 --- a/Source/Engine/Renderer/MotionBlurPass.cpp +++ b/Source/Engine/Renderer/MotionBlurPass.cpp @@ -244,7 +244,7 @@ void MotionBlurPass::RenderDebug(RenderContext& renderContext, GPUTextureView* f { auto context = GPUDevice::Instance->GetMainContext(); const auto motionVectors = renderContext.Buffers->MotionVectors; - if (!motionVectors->IsAllocated() || checkIfSkipPass()) + if (!motionVectors || !motionVectors->IsAllocated() || checkIfSkipPass()) { context->Draw(frame); return; diff --git a/Source/Engine/Renderer/PostProcessingPass.cpp b/Source/Engine/Renderer/PostProcessingPass.cpp index 345147b61..006927639 100644 --- a/Source/Engine/Renderer/PostProcessingPass.cpp +++ b/Source/Engine/Renderer/PostProcessingPass.cpp @@ -195,7 +195,7 @@ void PostProcessingPass::Render(RenderContext& renderContext, GPUTexture* input, bool useLensFlares = EnumHasAnyFlags(view.Flags, ViewFlags::LensFlares) && settings.LensFlares.Intensity > 0.0f && useBloom; // Ensure to have valid data and if at least one effect should be applied - if (checkIfSkipPass() || !(useBloom || useToneMapping || useCameraArtifacts)) + if (!(useBloom || useToneMapping || useCameraArtifacts) || checkIfSkipPass()) { // Resources are missing. Do not perform rendering. Just copy raw frame context->SetViewportAndScissors((float)output->Width(), (float)output->Height()); From a989173e2dd0d154845373581dbc89caa16098ea Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 19 Oct 2023 15:16:14 +0200 Subject: [PATCH 19/78] Fix `UnitsToText` to properly print string with 2 decimal places --- Source/Engine/Core/Utilities.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Core/Utilities.h b/Source/Engine/Core/Utilities.h index 36339baf5..f8807607b 100644 --- a/Source/Engine/Core/Utilities.h +++ b/Source/Engine/Core/Utilities.h @@ -54,7 +54,7 @@ namespace Utilities dblSUnits = units / static_cast(divider); if (i >= sizes.Length()) i = 0; - return String::Format(TEXT("{0} {1}"), RoundTo2DecimalPlaces(dblSUnits), sizes[i]); + return String::Format(TEXT("{0:.2f} {1}"), RoundTo2DecimalPlaces(dblSUnits), sizes[i]); } // Converts size of the file (in bytes) to the best fitting string From 0ea00a09cabe4bf4c6797d47fd92c25ea4c1d986 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 19 Oct 2023 15:29:26 +0200 Subject: [PATCH 20/78] Fix regression from 3e7368b1cb31aedfa37d9cdae1d8612b08e99967 to remove scene lock when spawning actors in async #1743 --- Source/Engine/Level/Level.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/Engine/Level/Level.cpp b/Source/Engine/Level/Level.cpp index dfd5786c6..d7659ca1c 100644 --- a/Source/Engine/Level/Level.cpp +++ b/Source/Engine/Level/Level.cpp @@ -975,6 +975,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 +993,7 @@ bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** ou else SceneObjectsFactory::HandleObjectDeserializationError(stream); }, objectsCount - 1); + ScenesLock.Lock(); } else { From 2f3685c16122afd4985aaae8ee550b19d99f56b7 Mon Sep 17 00:00:00 2001 From: MineBill Date: Thu, 19 Oct 2023 18:43:13 +0300 Subject: [PATCH 21/78] Use PrefabObjectID when setting/getting the actor expanded value for prefabs. --- Source/Editor/SceneGraph/GUI/ActorTreeNode.cs | 12 ++++++++---- Source/Editor/Windows/Assets/PrefabWindow.cs | 5 +++-- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs b/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs index d392e8309..163397768 100644 --- a/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs +++ b/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs @@ -66,7 +66,8 @@ namespace FlaxEditor.SceneGraph.GUI _orderInParent = actor.OrderInParent; Visible = (actor.HideFlags & HideFlags.HideInHierarchy) == 0; - var id = actor.ID; + // Pick the correct id when inside a prefab window. + var id = actor.HasPrefabLink && actor.Scene == null ? actor.PrefabObjectID : actor.ID; if (Editor.Instance.ProjectCache.IsExpandedActor(ref id)) { Expand(true); @@ -171,7 +172,8 @@ namespace FlaxEditor.SceneGraph.GUI // Restore cached state on query filter clear if (noFilter && actor != null) { - var id = actor.ID; + // Pick the correct id when inside a prefab window. + var id = actor.HasPrefabLink && actor.Scene.Scene == null ? actor.PrefabObjectID : actor.ID; isExpanded = Editor.Instance.ProjectCache.IsExpandedActor(ref id); } @@ -301,10 +303,12 @@ namespace FlaxEditor.SceneGraph.GUI protected override void OnExpandedChanged() { base.OnExpandedChanged(); + var actor = Actor; - if (!IsLayoutLocked && Actor) + if (!IsLayoutLocked && actor) { - var id = Actor.ID; + // Pick the correct id when inside a prefab window. + var id = actor.HasPrefabLink && actor.Scene == null ? actor.PrefabObjectID : actor.ID; Editor.Instance.ProjectCache.SetExpandedActor(ref id, IsExpanded); } } diff --git a/Source/Editor/Windows/Assets/PrefabWindow.cs b/Source/Editor/Windows/Assets/PrefabWindow.cs index 4face0dc0..16f7c21c6 100644 --- a/Source/Editor/Windows/Assets/PrefabWindow.cs +++ b/Source/Editor/Windows/Assets/PrefabWindow.cs @@ -149,6 +149,7 @@ namespace FlaxEditor.Windows.Assets // Prefab structure tree Graph = new LocalSceneGraph(new CustomRootNode(this)); + Graph.Root.TreeNode.Expand(true); _tree = new PrefabTree { Margin = new Margin(0.0f, 0.0f, -16.0f, _treePanel.ScrollBarsSize), // Hide root node @@ -317,7 +318,7 @@ namespace FlaxEditor.Windows.Assets Graph.MainActor = _viewport.Instance; Selection.Clear(); Select(Graph.Main); - Graph.Root.TreeNode.ExpandAll(true); + Graph.Root.TreeNode.Expand(true); _undo.Clear(); ClearEditedFlag(); } @@ -413,7 +414,7 @@ namespace FlaxEditor.Windows.Assets _focusCamera = true; Selection.Clear(); Select(Graph.Main); - Graph.Root.TreeNode.ExpandAll(true); + Graph.Root.TreeNode.Expand(true); _undo.Clear(); ClearEditedFlag(); From c773c3e8fc59359c9f4ebed3e83b9c040922c7af Mon Sep 17 00:00:00 2001 From: MineBill Date: Thu, 19 Oct 2023 18:55:58 +0300 Subject: [PATCH 22/78] Collapse/Expand all node in the tree if the user is pressing the Alt key. --- Source/Editor/GUI/Tree/TreeNode.cs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/Source/Editor/GUI/Tree/TreeNode.cs b/Source/Editor/GUI/Tree/TreeNode.cs index 7e3af05bf..703079469 100644 --- a/Source/Editor/GUI/Tree/TreeNode.cs +++ b/Source/Editor/GUI/Tree/TreeNode.cs @@ -776,11 +776,20 @@ namespace FlaxEditor.GUI.Tree // Check if mouse hits arrow if (_mouseOverArrow && HasAnyVisibleChild) { - // Toggle open state - if (_opened) - Collapse(); + if (ParentTree.Root.GetKey(KeyboardKeys.Alt)) + { + if (_opened) + CollapseAll(); + else + ExpandAll(); + } else - Expand(); + { + if (_opened) + Collapse(); + else + Expand(); + } } // Check if mouse hits bar From 2bb8c82329a51fa37bb0f6fb8e8fc76cb0abcf25 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 19 Oct 2023 19:09:06 +0200 Subject: [PATCH 23/78] Add support for packaging editor with bundled platform data --- Source/Tools/Flax.Build/Deploy/Deployer.cs | 16 ++++++---- .../Flax.Build/Deploy/Deployment.Editor.cs | 29 ++++++++++++++++++- .../Flax.Build/Deploy/Deployment.Platforms.cs | 5 ++++ 3 files changed, 43 insertions(+), 7 deletions(-) diff --git a/Source/Tools/Flax.Build/Deploy/Deployer.cs b/Source/Tools/Flax.Build/Deploy/Deployer.cs index 5882bd366..6e5ccc390 100644 --- a/Source/Tools/Flax.Build/Deploy/Deployer.cs +++ b/Source/Tools/Flax.Build/Deploy/Deployer.cs @@ -66,12 +66,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 +88,12 @@ namespace Flax.Deploy } } } + + if (Configuration.DeployEditor) + { + BuildEditor(); + Deployment.Editor.Package(); + } } catch (Exception ex) { @@ -183,6 +183,10 @@ namespace Flax.Deploy { if (Platform.IsPlatformSupported(platform, architecture)) { + Log.Info(string.Empty); + Log.Info($"Build {platform} {architecture} platform"); + Log.Info(string.Empty); + foreach (var configuration in Configurations) { FlaxBuild.Build(Globals.EngineRoot, "FlaxGame", platform, architecture, configuration); diff --git a/Source/Tools/Flax.Build/Deploy/Deployment.Editor.cs b/Source/Tools/Flax.Build/Deploy/Deployment.Editor.cs index cfb2446c3..87a2ae340 100644 --- a/Source/Tools/Flax.Build/Deploy/Deployment.Editor.cs +++ b/Source/Tools/Flax.Build/Deploy/Deployment.Editor.cs @@ -127,6 +127,33 @@ namespace Flax.Deploy // Deploy project DeployFile(RootPath, OutputPath, "Flax.flaxproj"); + // When deploying Editor with Platforms at once then bundle them inside it + if (Configuration.DeployPlatforms && Platforms.PackagedPlatforms != null) + { + foreach (var platform in Platforms.PackagedPlatforms) + { + Log.Info(string.Empty); + Log.Info($"Bunding {platform} platform with Editor"); + Log.Info(string.Empty); + + string platformName = platform.ToString(); + string platformFiles = Path.Combine(Deployer.PackageOutputPath, platformName); + string platformData = Path.Combine(OutputPath, "Source", "Platforms", platformName); + if (Directory.Exists(platformFiles)) + { + // Copy deployed files + Utilities.DirectoryCopy(platformFiles, platformData); + } + else + { + // Extract deployed files + var packageZipPath = Path.Combine(Deployer.PackageOutputPath, platformName + ".zip"); + Log.Verbose(packageZipPath + " -> " + platformData); + System.IO.Compression.ZipFile.ExtractToDirectory(packageZipPath, platformData, true); + } + } + } + // Package Editor into macOS app if (Platform.BuildTargetPlatform == TargetPlatform.Mac) { @@ -167,6 +194,7 @@ namespace Flax.Deploy // 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); @@ -178,7 +206,6 @@ namespace Flax.Deploy // Compress if (Configuration.DontCompress) return; - Log.Info(string.Empty); Log.Info("Compressing editor files..."); string editorPackageZipPath; diff --git a/Source/Tools/Flax.Build/Deploy/Deployment.Platforms.cs b/Source/Tools/Flax.Build/Deploy/Deployment.Platforms.cs index 832d6deb3..a2846aa14 100644 --- a/Source/Tools/Flax.Build/Deploy/Deployment.Platforms.cs +++ b/Source/Tools/Flax.Build/Deploy/Deployment.Platforms.cs @@ -10,8 +10,13 @@ namespace Flax.Deploy { public class Platforms { + internal static List PackagedPlatforms; + public static void Package(TargetPlatform platform) { + if (PackagedPlatforms == null) + PackagedPlatforms = new List(); + PackagedPlatforms.Add(platform); var platformsRoot = Path.Combine(Globals.EngineRoot, "Source", "Platforms"); Log.Info(string.Empty); From a8f961c43879e506aac502b9cfd40f7bacfb214a Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 19 Oct 2023 19:09:32 +0200 Subject: [PATCH 24/78] Add cook&run support for macOS and Linux --- .../Cooker/Platform/Linux/LinuxPlatformTools.cpp | 15 +++++++++++++++ .../Cooker/Platform/Linux/LinuxPlatformTools.h | 1 + .../Cooker/Platform/Mac/MacPlatformTools.cpp | 15 +++++++++++++++ .../Editor/Cooker/Platform/Mac/MacPlatformTools.h | 1 + 4 files changed, 32 insertions(+) diff --git a/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.cpp b/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.cpp index ec7b8e7e1..42ef6fcdf 100644 --- a/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.cpp +++ b/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.cpp @@ -104,4 +104,19 @@ bool LinuxPlatformTools::OnDeployBinaries(CookingData& data) return false; } +void LinuxPlatformTools::OnRun(CookingData& data, String& executableFile, String& commandLineFormat, String& workingDir) +{ + // Pick the first executable file + Array files; + FileSystem::DirectoryGetFiles(files, data.NativeCodeOutputPath, TEXT("*"), DirectorySearchOption::TopDirectoryOnly); + for (auto& file : files) + { + if (FileSystem::GetExtension(file).IsEmpty()) + { + executableFile = file; + break; + } + } +} + #endif diff --git a/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.h b/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.h index 562b38962..432240d00 100644 --- a/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.h +++ b/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.h @@ -20,6 +20,7 @@ public: ArchitectureType GetArchitecture() const override; bool UseSystemDotnet() const override; bool OnDeployBinaries(CookingData& data) override; + void OnRun(CookingData& data, String& executableFile, String& commandLineFormat, String& workingDir) override; }; #endif diff --git a/Source/Editor/Cooker/Platform/Mac/MacPlatformTools.cpp b/Source/Editor/Cooker/Platform/Mac/MacPlatformTools.cpp index a1db61dbb..aa56a6b95 100644 --- a/Source/Editor/Cooker/Platform/Mac/MacPlatformTools.cpp +++ b/Source/Editor/Cooker/Platform/Mac/MacPlatformTools.cpp @@ -249,4 +249,19 @@ bool MacPlatformTools::OnPostProcess(CookingData& data) return false; } +void MacPlatformTools::OnRun(CookingData& data, String& executableFile, String& commandLineFormat, String& workingDir) +{ + // Pick the first executable file + Array files; + FileSystem::DirectoryGetFiles(files, data.NativeCodeOutputPath, TEXT("*"), DirectorySearchOption::TopDirectoryOnly); + for (auto& file : files) + { + if (FileSystem::GetExtension(file).IsEmpty()) + { + executableFile = file; + break; + } + } +} + #endif diff --git a/Source/Editor/Cooker/Platform/Mac/MacPlatformTools.h b/Source/Editor/Cooker/Platform/Mac/MacPlatformTools.h index 21d9141e3..efdd0b733 100644 --- a/Source/Editor/Cooker/Platform/Mac/MacPlatformTools.h +++ b/Source/Editor/Cooker/Platform/Mac/MacPlatformTools.h @@ -27,6 +27,7 @@ public: bool IsNativeCodeFile(CookingData& data, const String& file) override; void OnBuildStarted(CookingData& data) override; bool OnPostProcess(CookingData& data) override; + void OnRun(CookingData& data, String& executableFile, String& commandLineFormat, String& workingDir) override; }; #endif From 418e220c00297f7871d0cafed7e44277cb0d8f8c Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 19 Oct 2023 22:40:42 +0200 Subject: [PATCH 25/78] Add proper codesigning for the Editor app for macOS --- Source/Tools/Flax.Build/Deploy/Deployment.Editor.cs | 4 ++++ Source/Tools/Flax.Build/Platforms/Mac/MacPlatform.cs | 9 +++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/Source/Tools/Flax.Build/Deploy/Deployment.Editor.cs b/Source/Tools/Flax.Build/Deploy/Deployment.Editor.cs index 87a2ae340..78875c5d8 100644 --- a/Source/Tools/Flax.Build/Deploy/Deployment.Editor.cs +++ b/Source/Tools/Flax.Build/Deploy/Deployment.Editor.cs @@ -192,6 +192,9 @@ namespace Flax.Deploy 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); @@ -335,6 +338,7 @@ namespace Flax.Deploy Utilities.Run("strip", "FlaxEditor.dylib", null, dst, Utilities.RunOptions.None); Utilities.Run("strip", "libMoltenVK.dylib", null, dst, Utilities.RunOptions.None); + // Sign binaries CodeSign(Path.Combine(dst, "FlaxEditor")); CodeSign(Path.Combine(dst, "FlaxEditor.dylib")); CodeSign(Path.Combine(dst, "libMoltenVK.dylib")); diff --git a/Source/Tools/Flax.Build/Platforms/Mac/MacPlatform.cs b/Source/Tools/Flax.Build/Platforms/Mac/MacPlatform.cs index 3be0beb76..8b614b6e4 100644 --- a/Source/Tools/Flax.Build/Platforms/Mac/MacPlatform.cs +++ b/Source/Tools/Flax.Build/Platforms/Mac/MacPlatform.cs @@ -53,10 +53,15 @@ namespace Flax.Build.Platforms /// App code signing idenity name (from local Mac keychain). Use 'security find-identity -v -p codesigning' to list possible options. public static void CodeSign(string file, string signIdenity) { - if (!File.Exists(file)) + var isDirectory = Directory.Exists(file); + if (!isDirectory && !File.Exists(file)) throw new FileNotFoundException("Missing file to sign.", file); string cmdLine = string.Format("--force --timestamp -s \"{0}\" \"{1}\"", signIdenity, file); - if (string.IsNullOrEmpty(Path.GetExtension(file))) + if (isDirectory) + { + // Automatically sign contents + cmdLine += " --deep"; + } { // Add entitlements file with some settings for the app execution cmdLine += string.Format(" --entitlements \"{0}\"", Path.Combine(Globals.EngineRoot, "Source/Platforms/Mac/Default.entitlements")); From 770d21566aa846645113be4e48019623cd6488d3 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 19 Oct 2023 23:13:05 +0200 Subject: [PATCH 26/78] Add macOS disk image notarization --- Source/Tools/Flax.Build/Deploy/Deployer.cs | 6 ++++++ Source/Tools/Flax.Build/Deploy/Deployment.Editor.cs | 10 ++++++++++ Source/Tools/Flax.Build/Platforms/Mac/MacPlatform.cs | 4 ++++ 3 files changed, 20 insertions(+) diff --git a/Source/Tools/Flax.Build/Deploy/Deployer.cs b/Source/Tools/Flax.Build/Deploy/Deployer.cs index 6e5ccc390..4f12e769f 100644 --- a/Source/Tools/Flax.Build/Deploy/Deployer.cs +++ b/Source/Tools/Flax.Build/Deploy/Deployer.cs @@ -44,6 +44,12 @@ namespace Flax.Build /// [CommandLine("deployCertPass", "Certificate file password for binaries signing.")] public static string DeployCertPass; + + /// + /// Apple keychain profile name to use for app notarize action (installed locally). + /// + [CommandLine("deployKeychainProfile", "Apple keychain profile name to use for app notarize action (installed locally).")] + public static string DeployKeychainProfile; } } diff --git a/Source/Tools/Flax.Build/Deploy/Deployment.Editor.cs b/Source/Tools/Flax.Build/Deploy/Deployment.Editor.cs index 78875c5d8..e1f2ab4e6 100644 --- a/Source/Tools/Flax.Build/Deploy/Deployment.Editor.cs +++ b/Source/Tools/Flax.Build/Deploy/Deployment.Editor.cs @@ -204,6 +204,16 @@ namespace Flax.Deploy 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 diff --git a/Source/Tools/Flax.Build/Platforms/Mac/MacPlatform.cs b/Source/Tools/Flax.Build/Platforms/Mac/MacPlatform.cs index 8b614b6e4..4a8115fcd 100644 --- a/Source/Tools/Flax.Build/Platforms/Mac/MacPlatform.cs +++ b/Source/Tools/Flax.Build/Platforms/Mac/MacPlatform.cs @@ -62,6 +62,10 @@ namespace Flax.Build.Platforms // 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")); From 7906e26fe04377331db0dab7fd78141497aba1d8 Mon Sep 17 00:00:00 2001 From: MineBill Date: Fri, 20 Oct 2023 00:19:32 +0300 Subject: [PATCH 27/78] Limit what characters module names can contain. --- .../Windows/ContentWindow.ContextMenu.cs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/Source/Editor/Windows/ContentWindow.ContextMenu.cs b/Source/Editor/Windows/ContentWindow.ContextMenu.cs index 56136cfbf..1071e6b01 100644 --- a/Source/Editor/Windows/ContentWindow.ContextMenu.cs +++ b/Source/Editor/Windows/ContentWindow.ContextMenu.cs @@ -369,7 +369,7 @@ namespace FlaxEditor.Windows } var pluginPath = Path.Combine(Globals.ProjectFolder, "Source", nameTextBox.Text); - if (Directory.Exists(pluginPath)) + if (!IsValidModuleName(nameTextBox.Text) || Directory.Exists(pluginPath)) { nameTextBox.BorderColor = Color.Red; nameTextBox.BorderSelectedColor = Color.Red; @@ -429,6 +429,12 @@ namespace FlaxEditor.Windows submitButton.Clicked += () => { // TODO: Check all modules in project including plugins + if (!IsValidModuleName(nameTextBox.Text)) + { + Editor.LogWarning("Invalid module name. Module names cannot contain spaces, start with a number or contain non-alphanumeric characters."); + return; + } + if (Directory.Exists(Path.Combine(Globals.ProjectFolder, "Source", nameTextBox.Text))) { Editor.LogWarning("Cannot create module due to name conflict."); @@ -460,5 +466,16 @@ namespace FlaxEditor.Windows button.ParentContextMenu.Hide(); }; } + + private static bool IsValidModuleName(string text) + { + if (text.Contains(' ')) + return false; + if (char.IsDigit(text[0])) + return false; + if (text.Any(c => !char.IsLetterOrDigit(c) && c != '_')) + return false; + return true; + } } } From 4e9a739a52025da83ad2626a136b1298ce230b83 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 20 Oct 2023 00:28:13 +0200 Subject: [PATCH 28/78] Update GPU particle emitter graph version after recent changes --- Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.h b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.h index 84834a6e5..967dc0e0c 100644 --- a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.h +++ b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.h @@ -5,7 +5,7 @@ /// /// Current GPU particles emitter shader version. /// -#define PARTICLE_GPU_GRAPH_VERSION 9 +#define PARTICLE_GPU_GRAPH_VERSION 10 #if COMPILE_WITH_PARTICLE_GPU_GRAPH From a5aaa92d62fd3370c85c5a4be83db9217d28cf2d Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 20 Oct 2023 00:28:28 +0200 Subject: [PATCH 29/78] Bump up build number --- Flax.flaxproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flax.flaxproj b/Flax.flaxproj index ebcdc2830..6b3014e94 100644 --- a/Flax.flaxproj +++ b/Flax.flaxproj @@ -3,7 +3,7 @@ "Version": { "Major": 1, "Minor": 7, - "Build": 6401 + "Build": 6402 }, "Company": "Flax", "Copyright": "Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.", From e796d9ea6fbb57b6a9b3d412b7daeaeed041dc51 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 20 Oct 2023 11:22:22 +0200 Subject: [PATCH 30/78] Further improve a989173e2dd0d154845373581dbc89caa16098ea --- Source/Engine/Core/Utilities.h | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Core/Utilities.h b/Source/Engine/Core/Utilities.h index f8807607b..737e423b2 100644 --- a/Source/Engine/Core/Utilities.h +++ b/Source/Engine/Core/Utilities.h @@ -51,10 +51,14 @@ namespace Utilities int32 i = 0; double dblSUnits = static_cast(units); for (; static_cast(units / static_cast(divider)) > 0; i++, units /= divider) - dblSUnits = units / static_cast(divider); + dblSUnits = (double)units / (double)divider; if (i >= sizes.Length()) i = 0; - return String::Format(TEXT("{0:.2f} {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 From ab494579441ecf3c2a6149ec9abb6f5fa5477471 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 21 Oct 2023 12:44:25 +0200 Subject: [PATCH 31/78] Add log for build command invoke in deployer --- Source/Tools/Flax.Build/Deploy/FlaxBuild.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/Tools/Flax.Build/Deploy/FlaxBuild.cs b/Source/Tools/Flax.Build/Deploy/FlaxBuild.cs index 7d3a31061..b4e8b8680 100644 --- a/Source/Tools/Flax.Build/Deploy/FlaxBuild.cs +++ b/Source/Tools/Flax.Build/Deploy/FlaxBuild.cs @@ -20,6 +20,7 @@ namespace Flax.Deploy if (!string.IsNullOrEmpty(Configuration.Compiler)) cmdLine += " -compiler=" + Configuration.Compiler; + Log.Info($"Building {target} for {platform} {architecture} {configuration}..."); int result = Utilities.Run(flaxBuildTool, cmdLine, null, root); if (result != 0) { From ba39938ed5457a4de5111b3d94fa0dca0ab8edca Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 21 Oct 2023 12:44:43 +0200 Subject: [PATCH 32/78] Update Flax icon for macOS to match design guidelines --- Source/Platforms/Mac/Default.icns | Bin 193857 -> 176267 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/Source/Platforms/Mac/Default.icns b/Source/Platforms/Mac/Default.icns index 455acd991a7572831f097c615562180774495fd7..911276d8ee57429688ff8d8979e4b479a509c53a 100644 GIT binary patch literal 176267 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 z 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ⅆbo2SYSxSs!b_e3iF!khIKxj3jD+DIUkMIPHl+e}&;k z&_7u7j8TQCSkfce2Z3JuVX?=BSg#S+2Fj7x?~GW3-+{yKG)>{k*T2sCn6BTeNMg@a zWp9IIhT!npgespy=1b*+ptHZeDx@ zRIpq@ovP(XkEh08M#>L_2c|rg?qy<(Yj31x3;Pz=+42_1aFVfI(!3BxPkGgSjM75h zlQ&NS&C@nhH0ajGE?Bn#51V-+L!TJqz5ZR!DcP}b?lnmBJLXgI;Pw|-olHG<+gXYNzaNK@5xLG{KHYK zBd8QJ9z6Bu7^bON3)W}Wdi{h(=D>D6(#`MDH?sTtv;{t+q6y(HSEQU;Bp$MbT)t z5z45qRcy^Q0goZoJNPxd2MWp=4?d6)5!yu8oxc^KkVQ{Ld=4$9QTS55XozAYxZa|% zAT|b?TJGq%c|A#W&yKH+~xUjI)1L?48FFNdBS7r<5}8&!iDxbdTDw=6ix&Z&1*5J!~d`n*e6KOzG)W{p3*G3gxXi9cm-{WpZ_g6my2> zg6`d07TW^|@fN+q5bst0wDju^?2p5SU(*XnX&>R;{UCF2-*yBy0KH0FRZM4rg#eQy z$R{2$3W-C@}5qa^-o6xWLG_^pq==#{D`j`mTOxpr|fm$Hkp&W2_Hbk_0(Y!dEqP7V* zv6%kNKBVF+1TEe-g2GzyAteSLv=1i>K9Pw_PSs#{_+znu6I?6~gk}6H$_MhxyrmI76KfpHWjsPAnM~vBPl>*^ zGa^<(E;0b!0Pnfl_o8^{f~k-oqASEjvPVexqiE2sESC?-X*}{%bJIVR9zO-d;dHLn zTjNd9!y`D#mF(VD`$_%FLfMBqc@rKKXpT=a95k|;+(;m%w|N(W3eSz~LY%AYZ*{qh z{=+)@afwe}keVK0>it>df#?V1x-f{Q99G)dr`|A!>bSyK6rO#ld}keiCihz|T-pFC z>!p7!QSpwB(%KzpAIfkQCm@EPC|jUpZ{t@YU>AS>5s3tss$F{zoY6?($e%qfOs<6Ib#bP33Z3e2RU0IW?AF`;_X#Jdu zg(_ls2x6Dye5D$={xbeo2*Tis>bHpcEuwylsNW*$w}|>JqJE30-y-U_i25y}ev7EzBI>t@`YobTis>bHpc zEuwylsNW*$w}|>JqJE30-y-U_i25y}ev7EzBI>t@`YobTis>bHpcEuwylsNW*$ zw}|>JqJE30-y-U_i25y}ev7EzBI>t@`YobTis>bHpcEuwylsNW*$w}|>JqJE30 z-y-U_i25y}ev7F8|A(kQ0RV8uvscm*5P;aIIN%zi*oYV)849@u50MyzUV{uuLa%|6 zOY{ewC_(=MHn=&9F+}oje#lLJ06=E~{H`JAYis90*O=Q;3cNfoBDVdnv3^UFQP6Vh zh&})9dyTU##iZz6wc5)|BeU&ujT7$h=j{4=L4=W&`V->%>6x@DI=T0Xo|Tze0e!U2 zOa1Bk!jhbpQAkf@?Iz8{F&4(E9@@GS!i`R!DKIk1mCYfpA-UA@XNYU)Cu&yu0(7#C zo$?d9_-z?FX5P8$DFI5EZC|t`GSrk7t8yeVzy1G_kY7zYYCGsg=B0B#wD>nF{GTG5 zn>UK=ChN(|Y9;QX6`9uN=HZjJdi`b^t;nvXkbYR>#*o}oon3TI34XKZ*VoG`LSnor zH;QN*qmu{5{9+=!dg!A~dES)k3(v=VLN9GzZQP`Vr6%LWOyjzDW286His%Um>eer! zCI7~&ccVxOub4muI@$Y-Ck0*nt`5J5{NgoA>50xRT9IiTULN=Lx12iDXhqNxDw=2~ z@7^e~vHJh3$mP+|C0db_zJj#W+};zkB3pUUpJP+XvvRl4ik#%c7qm?uE~mxhouF&# zY23cNzTVG_PmJkDD{`4vj82{w$0a7j6rhie(qj6rFYBUX<1;EU4{y@($(ucirQP!< zT`5P`*OvwH@%exD(2|d#^P?5%k4cOhM<*A)eC|gVf07rQkhXWdlm0pH1g*$cUd-pZ z!~9QqTWCel5`Hbo>pMX!a(Q%c`Cp5e8FHgtH0nvnOC@PMYzl026XD6qNWDV4E&31) zz+vdGLziD?=r52x+K++aL8>)$LdsZE_PwGaz=l4C1E3%?0D6-J{h>gA005E(0wCyb z(9LgY;Q!8ofi%ef9p7ZcIjsx;0C7N8>e*`-(55Fw5>?j^PuVUy>h+iX{YJqF)OPzA zVl1}&*NZqpw(o?#8Gba@e(V?axZ8pp1IEq6b1)q;=kV}yW58_b=bFB=wUcx9N~KqZ zcKq6!*R+tX?oobzLa9HMk7|xm$GYODqvlzDk$Llr@E_y+T5E(w(t$KsZkZcD6p*oQ zOzS>79;s{c1^C~^Lilpzv#*}(=6KNlIt?9qbtta;PznJj@Tnu<|4K{q12U8{iIJ+d z`mL#x^t4bDnc;?V%%VA+aoz@bZq>tv()`>$ulm^Uy1Ir#PB?yM5PN^&H4i+{ln4f3 zAkY&0%vKfB;4Zms)F)fCdAEy9{8pNwbG% z1kh5{0`JxQrL72^j=1J7bwG|D>DsKJ8&&JqR`A<$bY2Vu4EXt+M(F*+bK_@QG?GWF_Gg|ESBgLV0^kH7265<=A5Z~vKSI!zN+6lt3darWu`vk9g!P2k zfuy&)?(4Yx*M~nOuO$FPc*_4S>6ZlV2X48E%X7&iYuyS}eSmC?p?k_$K;5eIIANm< zvfywvN7_V06Rj;BSgN@m-fU6&8Y%16|Jp!5StE_>Ein9?*6E!6@HCdj?+`KlU%|oR zmQZmvox-jlH;>|0KmpcC!iWLr9=0@hrF&=4yxeQ6jQF1n!H6a}f`v;LIer*l*a}QB zhO@mu3$R-~{^PHnh*vJ==J`L(d!u6F$7Zb}{fGGXpjEt;k8^aa{Q(=dx$79;GgXo1 z$^Y3A#Guay#?YE`cN5O@1^mGpq#8JY&Z(}*$%G5Kj9`Q|A)tY}CqkqCOut@RXig4< zstCWnez1C-gXw%>mF#K3_m8sE@XfJhv8*n8Hu$E!wuTNfu-b~`Kbak0NOfzrPW?}O z@k~O^BD@-9B%c~VZbB;a$kvD5t71aHVUIRVFbY#Rx9ct&oL^8jDg$l43(l7W z(yKmA|IZ#%zRebvtB=^As1nA|8v~z4-9NF;xnm0auf-7X8a$9Gusyn9dk-4fFs#yH z3r28U!KcqlX~qSxA3P2GPcR#}!f@YJrUdYbKp>l64jAsZhCN?a5s8dJ{--J80etgU z#Dz)#;v{*5UK);Wq%JnP>UG;tnwQt*|M(zUZZ13@qODV_`s*KqOHufZ|F*g)Os$ykb5PSr+T@H;t!Ybqt=CY&DC(IqLZc^b1-$z(~_1$UFKgK(@5$29RGD2g>F`t?^Jz2J2>)3 zr_a_<$LyfWJKcZlLK#B?Kdht*G;~e|`W1l@RoD+apIsRK`+&P~>`h64+}^spM3C^0 z0>GBwAxU{$MRRymZSSv1dee?K&+!jX0@hWb57?)}_~I5fH_}de_+JTRmD7`tj{kWX z9oUhfR8p+rRR99d(TlP zp;@5e>1*+ZITJS{sJr1?>HlRwz@L!&WN5W@$k@Pl2V;9rn=6AZG=K_jk>gu&(t%y|~p< zHB8+3nz~~CjoTuI9VF$yyAA>O$M9ij=;Im%TO6!d`IrKz3()?d&diT1qb^OWat>9-25SD>T3xl3(IxWaG>9)pPviQi zp(}4JVRQv$DYEogT~9Y(57ltgd!~H}+c~P$_w*TOZ9Exx9W0{|9J|=ADNi7y6c(j$ zk6_|`}xC>X)6qca2#|)K4k*#k_FajcdtDAd{&J>!4ar%|!tLectMYetZamKB%P&pPH)=G?-5lDKc^2$rbQAy8vfaJgbO)RX)4gh zf+oVkImcMH)E8%x?djJkG_!>Va$Yh};b4Fy*HQ@C-B}c5)RQmqrmk-I)dnfaJ#8tW zhsR?PSBJhk&w?#>9#=q2@H@or3(9Fdqw6+*;OS=6gK88Q$&#$Z@&Ws7?UqY8GwyzS zdi~|BKKJKQ4m~KX{B4cQop_LDFv5h8b-2TGEBmrcf9>s??`7guJE9+cZ`0%Io6wHx zkRPl+*4RPzly?VYFJPaW`Jgh7NJG7{iExBigY?rrzylA5Vyu!MGjR$DJi*g1l4=%# zQ~k>J#jW{Te1H7Be7`|YY0;LFc>d#zEPn|9h$l?|GM(dm?(Hz{~Iz;PEW1x;kGnjBq2{X{0+k! zunL5n8}-DHI!1yT%9yn)F&;`a>qGI)XCrA*R6OFz%KW3i#VbyiF@5m~UL?cI3O!O! z-OX>SoLb+;wGItbUrcB}yl}jt6aE+zGTo^u$piQRMv8EV-}z#93~6-^m;OAWTv-(A zRRnGpFn3EH61oR;uBq{z7BA{Cpx#3xs5#?m<(>L+epdCq(>-)vSE9uMIsJXTRqtx_ zW#*V|6#I1w2~2B+sV}Xu)^6IEIz}em1cU9v8MP*a&;|$TzY99cW?yZxT;i{bMImR=4ak9A2f)W`Sr&)sUDNiRC^p`WPIVy z{%$BeP>a`A3VRY(0XW{Z!W@5-a2gDPL`XS`$gq~P(FzgvrzbFxr#*ecN2m4f%mB}?=NwpDHM;36dvCh_26*!EyKS#S) z3%a9Rg;&x8$$bRi4&Pl~CEP(Nm738=0nnlZ9H*Wdtc{|kpomzx!ZQc^>YbJzR2qlj zTXj|qx-8=)9sbPl3)B}8-hKVz4k6o@Jimz<0I1xNxF*V2l>Y+`FgwmUv+LQ@Bt^T*Q zTf21NSq%{CkzSMe(4bEv=V=^ViU{=ZVqrAEdh@)%I6VJ?@GGXu)ckxp!|#VPU*Nw& z0O+fJ#du5-jz5f{yhy30w{P}~%Ug!tU?1TX}V^8G-fat@% zg0?BTnFx5&Gt}_htAr<>q2I5rDBhIYHr%-5K#b4t8b#|S!zngtgYHsbP%0gQ)lcm71?S&mM_uMe0UjGNVF5uqA?@M>z!=0Y z5#0Rz%u8!v=#UqO5RL`zU}0Na7~)g=bDFn)2dlI2R|5PL>hpG5t4DEm?U7!6I!^7Q zK`J&?ZO3oefVy}Z`g&w#HRqQ}!{a`3I9+`2V!|JzLvwlK-|Gx!*^3V*Pm{TyT0D0l zM4Z#nH1AT>tO9>VuE~!-IQp*n~ ziyD895{|YyTF(T*`SC#TMmih@k)si{fY?8%p{7}lz{5UAE!E#NT?PQ!_4-cx1ZJ}Y)Z!T``0=?Ob+z2qg?VP$YRx^S zEP)i4n*sH&5QJ!GmV%$Iu`yG(;_MdV?4{P7I{dK^VlfLa_~$6p2CMi43$OttOF=s2#_#Q~Ow`qq%UX}!(ib+F2;m?woO4Ry zs9ZyL>efLNMNC5U`&^gYN0mx;O4g`DWP!3<8ku`~=6?qdO@>zF)W z)LBZ7*}&zb+p#J`9Ffcg*bZGKm-zJH;aqw=?kU(;yqZN1R;xF`;?3|} znFpl#09O|a;2|&O!ioCe;*&$jI_WKu^2+hLeqmf*_6=|S>`z4LffOAs{IkYu*>@gc z9w!^qkGy~b9Dk6*ft9TvHb8qi1(08je+S{4Aw`p`)LyI?d$*1G#n#alE8H}VKMm+& ztXJ*s?ua%!#$>hF^}FC0TdqUrWoldrI!wcH9rXl>F@sGQ^)1ilu8|}8 zXLqhyz(aWVjiz>OXkjU!I(9=;vgTCi zz0m{#gUwSgL4TCtLPxcF_mFi;Zci2Vkf_g2aC02oXf8_`!X>Y#x4Y!*vE&GCe&V&1 ztVS_l{f$bE)_a2T4SK+9g9$#pWKeJ3@p>%db=Dx--!KvGON=LEQ4k%R6OprNTy+b^;Yi zUn*rTdgnfZU*w&n6Q%kl8#KFrhC}S{PZxH3y~6FY+SB?ErxThCHZahIQ^x2+E|qed znbUKmaoJVXo8V?MXyi2uIR$VEVZgO<7ts{{C~o#9OIXOMmPS6SqVQvt7jh8d^hW{e z0d3KnozV08+vY{`X1G74Ov8(zRB=e?4p>~s69l5uxI=P>p;SZlXy&q;Dk0O#i;l>4 ze5XaWnS&AuD1eO{7KSi$a3a}OVvw&=s`>+p}5ntAFDKCV~4HYf0qbk74$#OO_Gsc zTxA|x@>hA1IdWXVkPZXIEi=f8Im4N^U#q-0u6V!AnKPi6p>(Hh4|iKB`S%G-T&KfNrIyZdFGP2vUwj}t0O(a1Zc%nPA=E= za>U8N91DnQN$rpT(ECOEZBCj&8__{uv0Yxulb;yD<-X`KYdJx%h8=1$9P^NzTXwfl ze-8>yAOK{pcsvUAMkY|+b@pXcf;zpCje$tsdTFTdb6~D}*RVBuw+Q84B4)(D|{A@uzpcMao_y3dAF2LtO6-On&y8jiYesd6m zfmM&fuD}=@KpGHYf--pN<=nFL8R_ONiw1wee7p9)(oh@|ZW@y-2Id(RYkCgf7T0tG zE+q?D5sp44Aqcy${x_&Z3=2G@)u!h9&B)*-NwZd89=nV_*6u+Se7fF{z~vLL8dS}S z|M4T?8|;~AX$z@kl~0Nrr6jj;GTV(u;J~`4Mz9K7) z=-Wy{e#?sFBWY)z!!K|fHx=aB1o2Ls-=FS8Rs^c_n|m5E*q`Yy;Ir68l0ZiZo*a93 z+>J9$Lsw=cQ3dTKEC{`3KFw8$!9qN$@pWh9P~UM~Cl%$VQN1JnYo4!k_hDzgA<2OI znenZgBXf)Hu=UBOq?31Xb9jy)VuaT}TZl6+iw<@a-j2Vyl zQQ8Q>ctL*Fnim6eqf8}1HPL<~dD zN0S&HTq~|F8#9=tLeL%mIA<`vFVLIk@@8hJC;fKux z8lE=!6e|7>Fay_gn#K3yy2GBPK;7Erocuy+oFv+l?Q`GTsw4_NDm+^r4Eq z1^M;rj}k70dH7X~Dvtd;g;>8YsTs^=Md zd$-9sB6I&v?o z{=oqlT~1jfa!bKy6~P~$O!OT8Vb7(-OIQHHkY1Tr=|O}BS9|sJ8+e|EV(Mtwkw7KI zBt_8}DCIh64f!nIbQw@CNU1JtHN+;Tlg&o+T8`nVl%Ay(XgVeQG_LlmB7^?b#B7d~ zm9C~&xD-`F;nrVt^0!#*Zo>#fM0$&TIFL#bLrP1pw`5BL9`}ltyXJplzB;h%us)ek z3HNatrsnRmG&ZsZ#og=d_xu8ia0=*4UPF zw{SR0mQ!=1ad0&H1YwjD1bn(y+hYnej9)krYfwsfl+b6@kQ#Rx|M~MB-bVsj?nl@| z%-h<}y^!z=di+pXgowrj2OWDTQ2jxFag~GP(nYN0+Q<8R-jfcRtY;iRUId2}^koI1PJ^>NP7pMn?KC z-LzTs%_+AL6w_FdGko;%7)^ZasNL|5YQ zC0l>KPF<$W;(I10ebuf84^C`B`k7^08kuxP*#`e%oO{V6L)&(z7-2(+wWM2QkEsJi zysM~eu-B|*)F+&)eWGfO#y6)HQ-m-(pVDzh*r>UXY~)3{v$ z#Ou^jdYpauy;x_0fnm{@)&TM#M>p^*_!|OngeGE}?3UHDX`CzEv?iD%%n9evh!IBi zwCkdxPLUq_l7(VO)g*)bng?9mmw%<_=JUDf)g~qI`&xV(7JH+mbo@@7>35WCiEcA> z+J9W*Td)%dHe09Qc<*-Fxv}e`y0DJp(m~Nkz_{Td?YU63`)K@c4A!5{$a47vQzUy| z4wb#Z-!iFn`{V6<_wk_7PU{rqowBw&?2vk`E&o!mQWLJ(v2G|yKf%xQ&5@TD-&5)o z!G=5|*ezPW8ws;}CN}9m`?JnrK&YEu8bVJoCF?gD9*-4q9Lk@myhCkUuM2xYfCx0^ z8*xyVja)dH%&!WWrxD~{{n{CtEACL6r5_1XX?PIvPocHC$KW+q{P}Y8EB4~gUQeS# zov3&84?Oz@Sin1KwR;l>I-XYpDocp0u1WOJgO3Cdwvo!)4Snwob3!Zajt~`W``#KAeyj3LFtq0%Lc>E6_|nYKD3XsuZWzR+9Zq+ zul}~kNcZK@JDn=I7kDbUb0_!5jNnTi$Equnli)tbD3Oxv+4rzXFw;+6KEB*vYK!vr zDQG|WLsj}P{-yA@D#G})U@Y*m{|MoGjG{zg0+{#lI7GLlS+;iiNC8jr?X)7 zNkQ$uvAv*#m`qTC$P>E*lIN03k$xjqxiUS&U#MQ zgki>C{vo@*CS*ladE2Wxm7Ia$r;|q)xYHFnj&QK~j7d0Ileepk}DAu$v2yj_sUx3Y~OZQ3n8J>Pw->6 zX`2<$hCgN4S_;T8szD*rSDEZ_vL>G0&NtH>#FhsGD9?uEYzf4DjqmRXdq`Cwm^op8 zXDW#IuSH5{#uK7%GR*eH|;yyv$0|x23?b zTMIJ0h4=Hm%Y&hUH&v*bJMGFlZU2IxP1@ANf4EWV#nqn_J4k@^KqKO+j;k6GG9DJA zw=i}&`VW{NvZf6E<&#M3Xk9BhSjKk>upeUmTU!5Z>$P!*q&*Tn%uY=o9lqW!7BlD5 z?q|KO#W?grV^}LZx{MQh^vx5a`eee^aw=z|HrUyvVB~LIFmSa^AZLRa*}<`YousyB zhtF{(j@ulR^&oAzhff{*++Gu~DG`GuyAq~0n52mF>kT*GYPwdH$&7s&oKZdArxtT) zaNCLHl)AT4)!(zSd}KgO4@zZzlCbNZkl=MW_pPX-q~mR5+#CIoJ2Mz)T;S}{J*29U zAz^TuI|Ck!V+WIwZsT=6^+NVy&zhEmG{368SYI_6M{je+Zj{8-(Y(d+J>K!2&`dzb z)rulf@*>QALf`JC)ZJ-zvy(IUGo%7zm>rMrl4ZHF(8oyKtXzJkHxR!;@{w`G;R8ui z^<(r6*yNcawvI2?Q5xv#N}YE%YDq83Mf3W&aS@-9Wue_rK8TO{0=S068x|q9F0|?D zQaxa8CKqxN)-Bupa&PT$%Obejg6=kloGka4GHBN)F@#7F*T}mLjm?B{As(o;6M^8( z%V6xz!`f)x)76wXf@KT-hblM=fUL1SO&jm%G+$R?*(Mh<9S@yhZzgaFx}G?SCd$kLjuGJ+O*)*#uwkv+nxFfEkSlz)gNywdZ58y-ZJTW|9Yq7Wa7J0$oWx{<;}Y1wDRYj66IdP z!`~~gYB`5a>(b8~Pm7+vYwo^U_n&(afDdmbl|sr$NmZ_~b~A!6SZCwD8SkxsHbq^Z z%V6nP$mXEDcz*+?j)(6>f2gW|;=)M+|3^IDt&5$NA9}}svQr9n)be&M!f~J`_sG|y z&Of!t)tSiC7>@TU*HFYCC(L&IZkyI;<;7V|P;H=?6ud~z|D;0y%h@Nj26X(-9&bX^ zsK;ef4cV^=j;mciCd~HKuH#RK`Kaw}TQ=oVP0FC$8AqKU_vb7P?rHd0E(8kN+ZAt1 zf8GCJGA7+9H>sh6*aG{5Q>7;-nrObif-zU$Xkxod1|DaMmY8}>N9TMGcg>xl zn|eN|SU8zy|26+bP)@%$Cj9_RU_B>BVmoTA18yJK0Sxu?XCUu#Us*%Lw{twDo0T|aq=H0uC z$Ikn&wT(jbtaS_mrOVMfKD#fyq!gvsu52w1Qqnufwo(VGy~{6Ygc+_RcwQ?ivAKo5 zSvAY}gRA6j95Qe3AV0e|!>ZD|r-iW=KqIC9aI%w4o}5>`$?{)vzo?*`uuip3C;K$Q zhd^3EA$5-SvsEX~za)-|#8GvBR~M^O{NWopc%^`f}zEU2H zvwS!xDfCEP-pHreDU-Z@(5#TEhgEJCrX-~mEBpTgbd+V*N>WmWFmfDn1;^l!en;Z* znNS6;>TebkBqrmz;5{*3d%KL7=~k7(-juS(y#}ieQmQY6l2=c5vu8^TTh9;TESV=M zzeRxAhD$l4)Vn;}Zn2zl;%BShupNpC@L(OutkO9(s--5L+s2$YFch42CNzK=-Tk(m z=EtqEQeYge4!ZuA&FmsERAz7O(0R=(anwIMtJpg(t_6nvSbpI_XtS2HqlWGJ3gRNF zOx#)+7B0GH^GoOiaOtZ`EKhEf%&INXbt}bLs4Bwo#YCRc;WcS^squ? z%B;tKNutj-&Q8&xH`r>iZ`rlX=NYL)hNU}iT^hdLDC|;>&1#F8Ah8~Z&7Qh9QP-JY`g_+ONHE>oRg)cC=aj$n6BT?_2+O*gsWQyB55X zhcW5*wqOdQ@$zg0ehK1F69%dLBEq95tdFo-_5(eVx5m#OLrP&JK%g9%*u~7FLw@!#XF5!h$4|i#=2$)SRE7Amm1~PTxM0iJgylAu5Vw<5}|@WKM=tE z-M@AjD$P4uY>$K4U0S-k@9i`m5fh_cYZQ_s;h0)_j&U(OvmrYC%n>cPxJfk|WdNyR za-YM9>^0ZuCTF4e7&K0o>1ano|M#c z-Q}|>I^*8`9=fiq(u)LQbE>kCRpJ0w0$?54NXWNw9LI|lwVk^jg-_sSKN|9Iy3kg( z*(4N4K=H5G6%Cu9hGP-`XM|(bFATh0&lA1a3>{$Zi z(eb7K^t?>TqOT$)eX@r8JQyks&~q8-($o2MzV(aj$WkY1^I^Z%nJ`8FxGJjh*}VHG z=lg3T_f*4HGc@ixFI##{ok(PO*oi8GIKh&#B>DKp6OG29tInB8tSfsL2@Z4+YME^7No4ZY-)#AlRd~E+pQ0Y;GM(3P`6$#)%B2KDm z*388@gN1PA&niPgYMPs6v(;QK>zFEQYJJmZmF|4xAP7}ushsx*4u8>_q<%(MVf!c1 zzCi?Vw>gdPegB`nA(Uh4@sKGjJ}QWcxbC9kd2|m{5V&e28xvH+EydJ>i$r(~Pl~Y0M_#FWgeGgc7w*02-x)>&rOasp1?oV%(mP>1hheN2$Wis-hFLQ-(ZZ|^0pm3`rh^sd5-$=(5j>`u4aAq z@b%h_kk-&0@hsnA$XzJ!CkepuV+A?}6|&#zhPDB~{d-ucS#7~l)M^MkcCosXzhI&U0sKvlzRe1q?uJ6{mR3|Jt=##58u`oH`MU(StFDJPazT$e z9`}=`9yQpCiebq{yFsg}2|M&74mpQuGnF}F`Jd6_=-TiCh?z-G$M1@MhH!*Fi;jCV z5b}R#%Uur)E%ScdCA)bP`bVfXJEsN|qmbHNA-ky>9zt_2jX|;G@fwu%6ssyLrlNDq zZ#J67?EmN_$kC{sGW`b&rQUf)r&n|vc(|TiQ43_x5kN=R2#+6MUSR$!m$PXVm>u{E zmA02Ve*kF=ENDz{&^-niSJ%3G$HCLT`4(k5TFO_Aws(JqNAiA188w@xO?g1Y!1@7i zWS_x@)B&|r0*a?oWAKmAiSw2Our*cJ(_%d;*`rH!&oiU#jdpC=F8PXuGYw)mV7krR zt634#FQu`327(tU1gYGXb>g@8jBG@f#r~c~JqdmfL=YuxZigfTm`DR){7wAcvpD zIn|_7!y)UByqV>>WYAyG@)aj(usg(J+*PVn;n?wz43&;LsVWv`t?Fo6P!#;OSAT%E z`yk8w66|$Djb!1GFs`UZ zzFVsAG6hzga|OQehcTO26g?Tp+BrQ!(rv@0g!Hx4)H;-UYU?0$W0)u6l^@%TD(cfv z#Px8-CL!-5jSy#{5N7x|9t~?!jO3!tXYGeWIyB%*sS~V5IlYm2qYat zC_{{}fMI2I)>=}%$fv{02+*njCXIq5(G?kF;-Os2=DmCnsCCmcedaLkLlh9p1ivQt zl;T}<>1JG)+Hg8UkD`Y^lPQ}$NMZKf`nrt<$Cq5DaGC82*oQNK( z@+I79d;!_WSDwPLpbr+#TZ4EXJB3GncF3dGr^hCA;)Av&o**49@9Bg;{7qd=%{Z}G zJa`R^KjKg0tXcBrTBE^9_r6+ew)zn=A*Thh*8hI9+|4|g-U-_i4agyckki8{kjT$A zAqobIMOy+34~EF43jV|v0&48ttWv;A00g9+u-{e&}K)zGP zezG`~9k~7ymECMWy6W+z;gX|8tMA?S22f}B!s(bgxA9vU%j{B$E_t3z9`PIvj`s6% zDET0?m+FQqc9^v2EgOqX`#G1@C+rPg5HM!^iXdVB(4D3EAxeG#CKnngbVUsL)Et|F zW~5`po=pO<*s}k7If~qy3ME;vw8VKTp^BBp^DClDY4*Z9>Gb1cN!;VhM19N)lmG{FA4}4C_*->)m9i(ZC4Mc1c?&7!qK7aL6Y998k_|oL9dQ)_gE_71d>6 zQ)08|(>U9B!G}H*98yISh=Ao}+M#K@4ST-6%J8bk3Pjl3wFw#~?D{pJz!XwRn@rRQ zyp)L1e9dK);u=&11f;g?7zvzFjeLnCkpdR?uYI4*!3!AyK7^SHPSX**X#~h70>Fk} ztOVYZ2kN0<`O3iAfr?}>exbQgjx$3UULnlx?IK}nkIT6#*pvk2bC{^?eDL%T@+x-j zZ&HsQJsMDi^{&>o#U;!y2wh9y{sW!wXG!phF^Ht_si9^3E(=meIfc6X!`JxK|JvUV z5yC0gBGMLR{pp@BBlw`e2Qj4&t*)YXU7f>_KvG`vPC*AvzoKJTMgkYqQcC1x;)(I! zg;n(-nZLLO;eZPY0>^1B))RbgLSV*WiL1@9=dtQ&HmF#0sG{>NigqpvzbBr=(8Om5*hHhJMY*Cvf7KrTYB1}lq%?YwO#p0hEVF^dCnfRs zr|P@^6w5u4A7%Rtpv4zqmGSIwct@g*gap(kZp6dS@OUu_j(*!-!gj*9HtjMpnb zr&d3my3sv6A}Sf5#XfPo$PxSZlu3ip>)hO89fuar(tRoEfv(x@z?573raf<4^kZ2b zBVeu!zviMfLM9g@0c#`eC3Cqr|L4x6;Qsv18m_q4YjSVDEIkZ;!j;r~#zPhM^$VsK z8l;?*x{3Bwjzw!7HoqrC6AM)-u`~NRu^Sl>n#%KwO|_8w@~qpMY$&)SgIO|~=dl-4 z%yARkL0a@H{pnF#XFw!5K*qjmcaVr%2V~x)A=uUqVrF>E*C^$#zm;HuV&zNkhlgn> zA2BxJF%?vYZ4Iy4B<;ou_>KnPFn&V{%mVm@WU*R`V-`5v0IC1}v4LU2~J1uo}R?p~6W-y)N8Gs1~Yql1r#mDvEG7UhsroJB`TnPUJ8 zSoEN&*+;V6B8B)V8IJcS4Gz95@R^&u5P8h!Vxm>-eYz^ONRLs{Ub?!T_@6*D*1p z2?M;~ySm0u)pGr+j$9-ukX#;!?7AX@tt$6#$PXZvcd>?LQEeHWK%2(B3MJC#L&w_z zn2N=9I%Z}R;04eRrt3wL0MX>yLyd;7CFFr}2^l*p9C1tT@B)vnn@&?yu(=0j98Uv6 zmCNH5{-d6X=^&pNn~I;TH4hAM*HEwm@MhB+q>l~?eE6$c<&oV0V%*As&-t)cRfhg} zicQJwUHS8MDa?JZ@GAnKs8r2-n7%kOy#Ecvq>Tu`FH4{N$pR>Ar9#Rh161t>q;b!C z{@SDdM}0>6+fz~^SF+Lf{Q%JF)2(M_D<{o_2EWL_`|&mg6|nup**kzuw&!Y|BH;0| zONb(%<;Qgwa@gBN@l}RTEVl3CN=rs|t%_+zlc)GFvI`DoVa|mE^}rs^1uFMD02uywwX&Y=hAeZO zxZ_2aJl-()c~I7QcHQEQ`SkbkFIXE!By8YX$kA>7yAwTpdwWb`a3eTmM6iP5Ik3+v zTp8f>?v3lN$as1gxMP%;b&b3dC{f0t18YJBmYOc(-%akx<4+uqh3o9{>3nN0}E zfe;sCt}|3R4{su`mmUc5P;ajBX!h$UH#OGgHP59EGK9dDfv_JetRU1@pps0BLa|1F;p`&M;Em{`M>2vwrbFgYw zS}kJu`f4A?Wbi;1_+jxZWwCWV)=*QlkaisvuA+}02h1VPzfLc&?aQ}q;<`UE=OGUG z@)L`F0Qr{parCLl<;#6c1WvVghPMbLk{D>*7iM3<1@>2u7)49dtZ)oi!Z60o$iNJ& z+-)!HW1v&ld(pA~P(*$jhz+E=F;y#)2mocvH-9?KR6~JF>p%s0ft3GLonL%s1+oc- z)u{nMPtr>Hp?}yN0k!#*>S83sjLRSb5QoHCUId#KYJATo!r)(6{=L4r3t3z6CTk2C z?3W+5o^1N@I>!-bQNsKCF9x-|_LMfdG!AV38I-rMyxuecA7V%*i?s^#<>!EshHsX5}0eI;Je*%QA06~J2}dBHdk5(QSSX^5+GB| z%LgmwHyU@?pYZq7C!(Xd!k_mi=qtCFF!G{T9=fLDM{usMg z&jUpeY8umVu|T-zR;iZ^q;q}M%u(pGp%JeCy~RzfNEq*9mYBk)G;d4z5_7`!tStmE)_RLdB?~~dqG{Ng zpilv~?2Jx61r$JiDm-3Mk|IO+4m@?JPId!|1k7`{60Dukp<0p1yY6c)hV@m-S3O!@ zIN%xqU3Hsb3K;?g6ifigM-l{RvQOjXBs>V^GGANF;Wjss!}xnXn&)x9C?Po@#q=Oz zsyv78$OsqMLddxGK&Y7j6YD;(k@BAE&gHz!yaPOW|Nkt2pZw~%-jvn*uUO9o0-SxX zdh0AZ(eF(ok-yeQ#Rvih2;fa=to;-afG3^u$!O=ta0D+{IF+%QK={xXFzgTlQQp=C z?Lwvv_ftd`8Go(2o-Rh;(d2B4d%Ane2qT_M<^)b7}7v>BB@qH zw3TwWf{+rjGF5^pO^Sb@6JEIe1)Hx~>Cek^C?@#?WNACBmakU`4Qi9Rs5(tIOAKCp z`QD(uUHXyFHaSxq3?p;h+qg8Lz(3H2R+N}(z=|16N>0sTC|3A$ymM9C|9UhVdQ{+~ zhA0h!q!DA#FE}&NSOmqEr4dQTt_e7$fp5<|o#%K(pYvN<0i=BY*4DF|r^S3jdA`bu zqxA7WHtC}I350#C)9~@FUjhXkllT1aM3Z$g&Taxn(A}0I=ws9RRhzRa{`+)1lz`y$ z8xi8q_tqH-^+*EX$ImkMVKvf%5S)m*R!9}xnOJCudAlj8jB|Ibe1LL>s=0RMGZII) z?F5cM?FB`NKzPI29w%Mt!*#RGTcz+65HYfVb6&r?+kSg=m=>Q&;e(D>j6}K(THqRK zQ3wXI&!qAiI{q5?=Y;vx`ZIMpxdJ9&Nvf${Y0EIJRTm6E*035Dzn7v>6iW3aCOb1U zW0&=z9Ip&+4S_DHmTOm0iWp$aC#kSzpbo9=Y9n7~XG&7@KYEej!Nt<#=+j$OrQ*rX zm?bU{Z*sVBloxvO=cN~!EFdioC;M%T#q_7OYx#;t7I>lgmOu|2P_ds)YdjPBt8OkS znzl{(@evu~cY|g;9|ACPcRrlxl|I^Z^qZ{Ad=%W}O|H$z0p(4ic zkw9~=Y0U1=>^)4AI3>IowtG{Q(|n~+{nLf zX4$hd$RkpCoHpvtHaA~)QXS(9r7idUW`U)?Y(H4`;9>z47 zu9YvZz2IYae>4G|X-7%StQ>(t3_UBbS*E&*+wb3dq;JKn>C<&tuu~z*FTig{f%C>< zN#u*aVqj9Ig=OA6pAeDRqs)C(Q)dMpjpUMVYeQx>7qYQ)jpiB5bAqnRWHeW z<^vj40N@U1-}_w z1-)Du3;2^3Z_*_RV%eEe%?;-}D=)=`1dcgPcvc?~%DNq@^3sFL6(x$U)= z+LgZGTnFHe2rv1V9$u^;8l7m9*ot<#k1{KFjzO7Cy9gsMRy=w?fUIv3YrbB@=d!CO zgb-YnPW9;(i1{^dTjsO8!Eac{S4);8yFc75O(Dspk&4~YWL<~#fi(M;??rU@+TSBP zzLC*nVB-$a4~rrIavulb+~m^NR*a0!qek^gE7V;^{$8)fkC*3S#dIT)F+Dgki~`<#7h-ZOrU?`h2BEX9NhI03m3r@+rIh=M3ed;)7a z+t!*ReLVuiUA9RT&l!R`YHGk1nFsG9bx`; zdkSSCuB*ya$)2d85xD@nvY|QNM3DU#wBG)Cq@8?he7_SY_-8)$U3WcnQ`UZ#phQNQ z_8((azf6Pjm?%K#1m8I3fx!EhhPmQ;@eJxtT9FBC|N`&np+qx~yH(ahi&$r-N4{nCDN za>gOO;eIIYNRps82MwJabYbZ(C$*?6Da+vAK7-~$TMm-z|LIxHl(o(mZjm@w%%~dBrRec7`DuDRItmT^*wj5*HF&8VA=XcUnb1Xo_nt#2QuH4eQ5J zR*gWJyGxWYxR)C*$J{|4g5Y`e=P(3;M%)(jI$PgnbN8L)fV7=-S89U_t&ek@!ds^G z)nz{M(~7|{T2%h~u~<|L!8;Ho<8YVc{dX}j`zCL1=5#*tKYg`Ro~6)J*-Ie~D(&TT z*=HC#;W`?Eays=hVsmCS=O|oKXzDHZqhklGwV%R+D551#@Jwu*9&RYOA>28Wy_u!V&sJZ5$;W-S<8$P6AmI$p69P$>$<=)ANS zk26%niv~tWlPH7FGGZc*CY@a=l%6Y<9i0?N=IEw2DvVAgu@4#*FpL@WQ#g1(o5gXVW1q(0K8p6=nyB^siDrdy$^1s_IPIY~ zm_g`cOV(~Pl;8ye8Xl=pd2UFJGmF#mgG!xy>b(f+E+Oy`MF&`bXJu+kaO%PCWf=)7LWz1g@NCr`K9fT(We>5jBuKfxbK!!EM9ry zSBQpz;t?uY=O6nAwzoPaCu-otDu(=#2Rv*^kDFwl?9xkOqHoO4HP&~p0`;U+Z%aRS znYWQlKMMm5Rf!KIj<4!D{L@EN+fB_<|owm9&9AuOz3#i~?v zB|A*^9H>g5cKkKO4bu?s6p0Gub>sD!7e`EDBL>G;TyM0lfZxDwWw~1@60( z&nmCNZo-3H@BCPcq^Cr(L^~cxni2}{VAn0Sw>&G;qm%Irv6~T3Z6k0H;**H$+gL#C z+)0qF!$L@Cg#YkP!`!X8FV&D4MBnBxA;A9oh@|k}3>dl5IffgOfhLVj#X2gGv(1yX zDz~h@RSK8sNThC{p^9&CU6H_ymOtH7s8_j;tsgD|#m7Is_VmoP^x?#h)pe0^q>`bn zXHCuazBGOUt7V;m`!T{mg#4(M4IS za8-bc&2LOg9+;##H93;SAsnOg8dDjioAXug{^j1hBIu(}2!7xmJ^?%p=5p{1GmH^b%7bU!{>0D(Uh-^mJ@da6IzetBale%r%nDYgCf zomRa0!c1aF`|%c*_j+%G=+(#^EQdW_GJY?MfMH?3KUa)CmdJ1}P@dY1vPV@uPR55zFW089K$hr;aNv-Rnj96IFa z@+E8NPwlY_(T>lWD3G6P7Vn6Zk<0$L*`(ltBl^wlKRjGBAzJjsa`33i;~tW({QNoJ zFr#lvM^&7I6a_Nuq{@wc7F#V<$mt}?HEGZ6fe!(Gt8(R3NN%>NGbc1QQwlVLgBJ4F zEe3Vytqrj!N1buKL5thjU`*2#^MCg_o>T)CcDoVOJlm=Am8aCOD3ait=?a(@C6?uW zQGqXF&rqatEC2DKU;s?i3mT``bqtU+4|hH9uwTxEf*ooL7Kagv=BA;+YtQEq^S_Dv zwgyZ?WBO@jya=p>RG9V|CRVuFufhKO46C6Y3rKQX&5eJ+I7(eS#8_XY;dvbAZ_gud z9msDLUN<|aq+|l}3kIAYsw7(Rd}G3_rzufVYz7c#Pv?4WGu*!As~=89T7uCj=KZs- zZoDp0pKPl{4KUr{Fe|8wzX-YfshI>4TULr}?&D{QsmpJo<-;Gf2-aa=eo`VEc=~Ah zUi?Z?Fg(KY zEG6&0e!b)ZR^o!)zS!V{$u`6&N2J|yqnkQ5D=ha0#=ynH(-l&fT`=2E$6hKSgm~!C zXHp~i1%#;8BMAWl?+8!?fXmf6c?TdQ{x#u^Q(%&vy&1TU8QSPUA&-#oxCuj`Ux^3L z$n$1(O7M?VmR?Na)DA14T2MQRnm@klKBQZ>DlI|FP|^4*0|k8DmJjr-e&nAv(W#9} zBDY*xY`mx=GB=Oh|A<$CnCdbGnX7OO_LkAyruO1VSvlG>-N{XlPv75PeJGI93IFbw zRFRVu*T>z~4X6HV2bywCzVvZ&l<^0i7?T_5nAh@-%zvU{0_s0V8TFw^E1F? z{X%GZ{xDhYs&=Vmw;*lpLbvm^$oAPJY1VThk!n%cWWM`k-BVQ)|9n71P*g~_LvHFn zWQJ~e)6*v7Z*vRbOYtQ=$AFN#&Wpz#_2yL&$E@f^h^tKPM-wQjunu4XPNOz?`y(JE zDBn9?*Nh73bA&G(XQ|E%BSl}Bn>d3C{n^md0J^%2S1F?QRPcgzjlH|2GjI}3zUq}0 znSNK!;jxd~a6Kxds3Gg1ggStC`DFF(`ssgDLjJ8{1#4m!vsilYD*$kE=~}!o$mo#O zm&{V1%2N#tMtJ6$Dp-i;`bP9^2D3AuzPF&xV-m$x!?AiLBb zF<{Y=;Or&I!WO8)igU}Y(`&6SXQ0V#NDX%a1;K|^Ppwc7<98rsXtVZ`7L=$3Tf%@;_Kk^7(MVPgIJB}yf{5GShX7A&U2 zCmX?u7@o0vTze!nW2!k#8nnNcrMBWq*sa>S(X^6jko7*a`}rc2!&*dHrYL9mj-Kx0 zb}6x1n7kKj$&mJU6lHutzb;JfDq=z_=$it;$sW*+S+esCms- z55$~gAN`N`#~-wgsckZ5CyJc~6agFaCxAj!%(iII^2S|);E+IyM|>5IJx%gsN}_Ef z@_n7fO*-Ru-H`zJ6Oje5hNPiud$8D?-Pp8AHJm%qd=(^52Nn8KAg$((Z23Ci!XBwqpC|?@+VAtlucyS_aqskWD4Jrr>UAV< zWGRtUuY1b+o7VK_&(5!cxZ5Z{KGCX|D89gW>0A}TGGm%*STz>A>o2uWH~>3%AHtnU zL1&JZSNf2Zu}Xc>*NcM9sc?s%ZIaJTKCWniW6^>XB@>fZ)2>~b=@RPDl?q_M8n*poBL4CQi zh1Hgu4sh|Z;rUml5hSEnXCPkxW$jXlKHdh zpLD;(eyZMTV8WR)PgT>t!iQIAvtVjv||foD>K&Uy^?eg(`;vbRFeo8g7tX6;Q3rHt+;^BR8S&U7%vo7g1kx>IUyLPTI}|T}fU494%G5!SQA#QWtq>P?Mv|3SY;lTRV`G zZ1XAP_iRp|E!HI|95Z>7M-}>+2?VtA{_H|Cy%}cO5r72e*I3Cw&o>YPp{6qud%i%$l_Gw-~VlyL2-x9L)0G zK!5)xq~vQ9rDl*=puv(pxVR#yMk$0rz?#sUM;`l{W&O8X=HCF|&{L0>4mzW{Q_Uee9C?o1PCtiYpV7I`~;E5b1*x4c(1>~_kpOR5fzM}a(N zI0oA~Q({(Wk6O)VRT7-P7P`xDRxDY-`KoY>h7z-;2LGQj#DgPeeu0XWph<+b___Jo z$W7Y@*e)38O2?74#5$cnDVvHmF0l$nWzN~MdZq+U%$ff5a7bmP9i+}|gU<6m&S#5b zg^B6h6Lpe`61g2sBnvcY%T|{B=8GZ$xFa8?RX_J^jBZE2km7+tEUNSkUk@Pu8$s4Z zo6hk1!(Gq|kY`Uf^^p1X^G(F;zBeVAb5$4TeFw-KMt%mj1TNHD+JpDgyuTcy%dB#~sN=Y5XV=6>{~NDk{rZ}L5R(u3T2p$!5zPVi z;b9h6(jPfzbN->WvZP-JlADkY)J}W0+R!mVOF2{fg;8@?r2M}vVG-ZY8EWgWZxxhb zSG<9ByUi5MU}(_A!)kBWdyy-rgWBX0a;tVJIkL!qCWuXeXqnvhZ;FQ#6vzmzecaoo zzo}!v)eK(fh{x3xH7I>9pB8`!qUM>nBBRxYz&DZ!9^WzQ34AyKQJx_#hirN~AvzqJ z?W!LP0Z}0AnUed*jcd&i}cl~H0Ur5gqh$80|v{_MTG$!vNS?aUW4_f&chW7b?Fq6P|} zf6B!Is+Vh1W2F6pyKKe$K@>gr*Ejic=4qzCv|cEnoLFn%n=1U{UgYlu?U-S$zJ&Fb z-WEr?JarDPju`qbU(beFJpf#Ad=kdMPl?AcIc2Y$7MeRGup+NidAm!Lo{(h2arx@j zw0pePm>(N~;`kK0OoR;mMR2eq%ntGz&{07_MuHUR{MpxEkOm1SKDUK9M#<(EMs^FcQu z;x`odBovg`(cyGsMDxn^K$zwKnEDF1rn~R|&&Gh!1C)*d5+dD_qfw+&LO`S?6zSLo z5{iUDh?FAT(o#bNR6vmKR$4$h_y6!bkKg}~*LaPw`?+!MxpnS&zmHxY>LhXs-_ydz zBW*Py*{2<#NG(E%6=uW}-a920`N%6tFE)thnZQG$wu?ANCmGc0{3}x80T?EE#@dAt zD%MiF&OzcP2#iC7atVAi8}Bop$#?(BWozj9c}gWSlr15#0_oYAf%u z+tbDRk>ocsxAzm^5*Xm_Y4~=$t(3qy7p5BeT0^G?FsCJ1aMGxuyq8S5auZ&p+izI8 z6YS0d{oEz}SyWVzd>=+Ov)?dibD)s#7n@9}d4J!W_UW<5Bd zdc0(AXFW&zeVzg~`zy-2uV}O6k4Ds(HfI+6!q>88N?g5WU7khX_*R%%EFkq%@a2L; zwo?W#67-`2l11tWKGnW#mxE)dwmyNkodY$h(<>4|tG8kN5+8p~#eaVedI5qIsSoMQ z=g>YS%j2R7UzjF+Uh~3$&;ClFCGj##hjm-quoNMdPm*wbgaL8kSeq~#6#>!&;>bLO z=n?n?_Hb?)m@S@N3TZaK@N7Rpi69Pqes}-V+maPZf5*|L9cV5_*wOuE?OrR$+jL8P zHv9(j;tzVd=i4)nA(0Ub9PPqP!?`5Yb!+Zebe_2=sfO6)NEh5b%_r{g9lrku}- z$_R9LvOXSE<&wDV$~lXI(YZ-=;}x&>iamI-&3+1S9O{8cpOrCg@xz-|zBx`X%(E)k zHGX6OAoS*~lmxx90!Y9zz}kP8=E&+Dq^shA$Abkhu4Tiu9MX z_7)s z#q{@+GQKTd;nM7~8Yh#QD1r;zF+hTzz;B+RnRzjKXG(qct7z2nbx4+pY!4uH?rU}X z@(aR}>2kdcH0XO|eI3K*UKPJ>qt?+{+R}m`&>4`zV=Tt^!a*yy*%fhou~T3ipJ5)u z2cPYq?SY#a&a0-)V#Si)1c#1TO?;TL0?78HO*?AY3vsZW8_9HD`H%CiosnJ$Yi>i)++EoD7E;lv#YVO_*GdikbNjnaY=;_ za7`$ZaMbDjNX7`?J_qx%)IG!>r$OGAT z&*`s0U}^|DPp;W6hQlpyvae*@`^M4En|8^~n-%p;|5#lp)dJdY)Z2PBSM_)@ew&bK z_Ru2(7}7lp8Ig)osZ=4IdP9BEB2qjSii6LvFLT`~&U*ZbIrWL5Aa$DI^!dH?-=_+A z=TZXJAjPl*!k%x^WxrkZB7{oYcPh!}*n@m}uRyxnUyhdM7G1{8KbHSwBWLRNGkIFCkt10hXy1pJAkX0ddp+{vly#m*c^bpC+K2Meifdq(W-ACrH7!94Z zSvlJ5-=@rxksNgh7bIca@DuLGU`qeHw|=Z-dL zA^r5hUxFUBf$jB3yfWe};EYfO>6He{xE&Jz^erDfv|=3lhhP9nzSqK@cJ6`w-U2R0*6`0i=YC?bEaq#jP`SObb=5fccD^+(l6a!Xt9LdQA z`=?7;T?+EL$Am~87;8oa@}wCLp>KBigUp3fDk0ZK2K7MLgZ|!X>Qx)YaSQ%$MONbe z_nn=o0d}Bx{U_+NVUeX^al(*pS%xl;e1;c^Rr1BtQ@RLR%jQO`#C>m}AjB})@0P4J z6fl8fKoYsVt8LSPqXq||u@3Z~16S^9DZbU|*R_evXXpkI_k6a@wHd0GF(broOb!!> zb(eJfL>O+_RSpR-R2D)}7&o5vCyQ>yQjPdC!}q79V5XjFnv6Q z$K1Is_C8a3;Cz^v60E=%)X$TRwU4Ab1j9gtFd-vBCTrI-Y#OPKCh#`b^9=MQHS9oF z@w~fBq5#eEu^T}z)CL8~{zhAC83Fkb-yGsttpx09G_=Co!NSBXTnJ;fJ4tISpuM-zO*aT84lOJ#GE@ zpYQ!IEikeP+xwcclnO_GK<_bka5DjiUEly)b3hko#9(Tu5ENhKna*iew5ilpe}Q(P zr^V9EqCKVBWRPHeLcvR>KL|!V7V9`LmdIprL_D+3 z0YdVnhMY|$F9EBJdTK=Lqe6){!d6>5i`cpBXEifjI&cM+jiithRsag#(tLh&)u@+K z?3@7j9%=M^HxImvs-I&bIxnEoB?b17FUQ4noAI$3hF?#?Pm4YAc0{<+Fh+u=>*v^A zCeUh806N}*&Z=nSmoaalj47p44dLJC%$S!@6v1rpLD7_8JfQUgRFx=PGH8T9R-Cut z^rE|%fC@mnkK5NnC9)KerV3xYyV1``w}!}-xZjPS))94-s6Q+|)e6z1R!xn752_(M zzA@CLG7`9jeHw8>Fl;YciJkXmQJ>Qf|F#LRsydT?ra*{^+@2}hU10XrbmguNQ?e-=i`qc%uehnIMbV20j>}x z$hUACGbeI^7a-nvu64Sy%ZEe6`&_q68)ilQKGBgx7`k;rye=J58~@(*mr_zBbr#Sk zqBvY@)AX)#cSNTD=jHB8%fwb0vV_4HQGVcJvD1qUvQD2W|Cx1q23i~q4MCuVH4rgZ zNg8`Bon=;Yxr>{;*85BlWX2GEeB+7eAAG2U{#LoB=~eK6s0uo_Y(cKsBMH^mh3=d* zfEQ-x4`w7{w`~M|{Fpq_yn9;YhwoX;n_0^?z`x`C0UMcm3B_cp`mP+g|7=t~GQ6W3 z4=8c$bh~sjEWdtRTA8hNIixqc(qUy)rTWBsu7LKvNU*u-9d2-7G=9?=U?vO0AOJiRKhO$o5T1gD1ASZCckQ!(g2b_3oy2RHrj0gz%8H9` z{ti@Q#21l%IgH=hvh+ZI3_JUstNbGAQx1tAB;e0aZXUk@qY#1xboazH!49R8^XII`{5pGYUT0*b zjw+ij&wQ!rX!6Sg`W{!TODmC;?bk37x zE{XN|#E3sH3gd21M&UpfnpfLO5zo+35T@r)dC_`|O6>>J- zu+v_%1|u?Cf+QYO>YU)`^wZ`-T_Cc_q|MUw&Q(7Jc}+(Av+d1S*zi?+kj)wgJ(VX& zj934W^%8KONf7~Ucy!B15wIs1g%yYnA@#fGvK>q_{BvLV_LeK2PI4YC#^`{LCvb%K zqCu|oq=`(`)Wu^*eW2V0Zs~C4+r`;e^cjUM?K?*kT6$*gO8Si?KgByvrk&&)IyxOK zZ>=Ah)FM0y-;cMPK8${!*S|DV+L{g6(!a2vnui9T9D9HdbLH}w3|SGtaDrYt`>H%X zU3Jaf7B~jHF0=9B>B87kCxMs{a3v|H(k{wXIuDM1w1P6i^prChuySg02Csu8nmISW z-V*q(YTNlc`{4wEIOyfm*M}TdGRC7%k8Fvnzdg$Z9}i#=G{SjCiry;ksY^AqZq!IP zx%*3sv8={{&WXco%d6n@==;|X;NiC8(COpyZA0Bbs_r9 zk&df6Vuh?}D;!L^`9;Tj-MMbI(OW*=)jw`Jj@Rr&%fDwNxSfW@83_v~J^*prt?}ZK zZLWl2MHGEVw-!933H~h;9Vx(}QggASEch*voU6pPU*H}PMRy?VMU1wf?+A$NT7x6E zj)~nC$11vD8BAJ>pJ@VMTiv&AK1~GaoUrd}p2Y-*leUaE#Ha$%MHmciG@}6*UI@mX77(mK(H#_ZvZv)&T;5C z$#X*u1I45}4;zfc&iob}xifBHYk!Xe3UZcq;6TF9F0eJFN1`@SB-r#I(9t<6{uODM zWQ7a?M9sGG_{ILi0YAnyr9mzg2=X&rwd62Y=Am-QrH*_2vr`!awtt*W?G5w0w78o3$R!;M$d5 zgzI-UwI1X5;)&oHi2`%Vm| zVAd)H8kaAP({gw(jxly73eFHa*Ob3*#)t>qW}K0fg{IvHZ0TZ)Ht3I-*Yx2kB8M@2 z04&~G-|UwoZrUP)dP14*?FONdW z1RPxYl~GR(K?4q_4n*=vYH;YV^WDLGQPC^kz+)Xk`I6f@H`5OWHvt40AD_B9p>isc z&*!%6g!y5HlW*@wfrhTBsi4j42h&cWmINtB&42h-d~hYu3xZ5Xlj+vS@lL&EVNy!AHO_8SxJoMBgh#~oKW3p%XE zXEexNg}Ice-!eD6&gE&dN8W<998G^44T;7Kp1uVQjrcf;bAo=B_Mt4hDh`72@u8Lt z%U~VeAG}Bi^0+%j`&QA*d(!xTET7Fum^fsdQJ-9cw}w5{Rl?v}Jk|s+s=t75%~MM5 zKo3!CEb4;C9$BZ+5;WsIA6H)W_v8*t7iW;Mx7;R$S_tPKRi#s*8^|ta@w7mvJq^CJ_;2uojH*0#X7W;P<20uti87|bMT9=6gZLD_gN@q6|h%oHx8Ej znhP3dYv)bMxlpyO3!NEo&wJHrwB&Zw5SlE;m61H*TE@&`>V%sJ^9GMRYxL5suJPyY zg9Z9N-Nf`rH<_5brr}&D z9{X0Rqjaij8=vt;;W>D`9OTR_?b{ElSM-oMmW{9ldl~V}*Q>g2xj1`czB1@zI%J%U zAuDpbg8zm&=^+yvflHI_+q=-9f^Bqf=XVfIrU!O5MlRj^?XISH%lg8lFjW*3W#AQ^*RCfn z!WI@J#}C;bsK_~=W+KH~6l+ui)U^S=_-FAWYq{JL?tL#d2!GNcC|L%VKpa5qm-8~3qrCuWfen51hV6F z>K$bEyQGF6V&XRt@88u+Zr!LOzw_a)c0%D#H|u7bmVtAr6|SzykDbc~1{@5qx{;Qk zWby|35Oa+M+1C75t`889^LA(5ZKObJU0<$4NW)U@+;FK$NOsx%h|fH@98AfCciL-P z{zmS=(I(!RbI;RTmd&n3!)FA$_O>=dZ+$-M)CYi`$VPAhEXXY=MqsI6(jDhK)5l|_ z^f~pe22#l;J@g&^N?m*)&pNbm>-fcEjSBXOdmeB8h&J+uZmx4*k$46u$e-}-oA{mk zMEf{TAW@P=Cf|?=1=lOZ@?jf>GZ}*S=P6W6?81$+y=lQ(p-uoQ`MvsywT%2LY-dZ! z`}61|bDBq77kwT=dy@Y|n!EzEUr}k~7{`#3XaV^MJ+Ee#(b`0vcZdWb4x~)Uyq=Eh z-I6!pSilYkagN7iy}XjD^=n^?RF(9c)+=1;IQIs_De=Dh20FqxJONzP`!e;l@=x_7 z4o8P+pr@XZs6iy7He|rD_=EI}4R!RfS`kfVNOh}6eNfWQ8;)4Kvq4#1+KX`fto?*> zn)wXT6P(>8poXwOqfomYMjC1^ z^|9iNHplF5=BV4W<`eU%tOS2pK1GZF?OX+hC^iBz$@3-U1-uB@g1i6%w`h$S%4R=o4ciL78-$?~Ff z{e3}q%iM{kTFP4NnZyQrf@6WHBp=DMuN7H z?CHaoY#B~tTPzG~tuCiCH)7Fahg2$-{S|t(y0JexpGh$B7)6soJxFXdt>mAweR@6J zh~Iy?zU-m%%tPifO$?ww)Gt20VAS$-g;=<1TS@hzChGg#>qcxWejmhj*b-a|n1*;~ zZ$Y$4%8!u*FA35;L4SHaxueJX9y~~D!FIx2(cpY|GL%$-s3SKfUL_VkR=$l7mpBhO zJbNYZB1Dst2tP+KG1lh4lWd>);?ey4WDjt!y*+!G<0*+m&WrClZR@)R=~L_?ta*G_ zh_LJmMBn|~mMZoK;}!In@!C1to8D~|y30k6w$bzEn)r71dbQ0|xAH zVV`ZyTM-qJ5(q(W?`fQ@rHgMfPkE-b>u*H?C((=VnS7@bJIAV%50Z-<(5W02IgT#O zwpIEk=HJzwSlwTW#%h|SkMk_stEyEj-;Kw{hcfxcW8Lf)@R3j0uQTH%xA)6tsUZ6l z>s)ogUldh2ev`=902Kv-mk!)%r-@la^V26j4P3Sic=)hN71{VG{I%snL%|@YU$y(c zg06+#RvzSMZfR%c4Y?Q7{PpvjyzbNEA${Y0kT|eykD809`k;u3U-}~~jU%gZIjNxk zl(VlWNZjd~?H5dqA?0on^*eK+WXS&I_1drT-LYAbkYnYAAbqZ1njZ%&!q9z5J!|7<QksNngCvh@gQaLq1@oSQjU+&!f~?=|r4?P)7fANUw*qW)7O6VU1SWdwXt zex*ITERFHxei((@GovVHE@fMm2>z*xtIdtR+4^;h;qaSWm-}$ztUq{FvG14iI%R47 z#5-4o<0I_PY+s&ky2XBNQG6})52|uKeJwudE=!U&1fvaD5NYMt2nUbu^e%f)#VUQ3 zt@T_SzDtWxR*@G=T)o~C)Jd?#P?!Fvxdj^+Q`%skYowLffqpa*wi9wH_{d|XPmnkx zN)g4uK~nc%<_%=4#Z2pq)E55j$${b|TZ>y&x>#{GEwoM`3xS3?gM^n3Z#Fi2L+94o zN+#;MI^r_z-Z(5Mt#062Hsqa;c#$p^qooJ|>C1?;zyh8h zJYN2lAALt=BF9s-Pp*^QKN=+~2J_8#P}-|*uK|L7pTP$YTUJVM*XT3h4LW;UR<5jX z;uqRGM;Do+T$_|u1rc>na0^q%vt6N5aJ72Aio67J$oCg_zJ1^Ve*%BLwqW@dEK_+ z=?>?wqE7{tLVB-CT|!Tga_XyLWPy4~O1X8B3}<>wD1AH3LaJ=37<=|pECt#=5#~#B zDf>wbtp~*-UE|N9T8*CpX&w0No$jn>e6jd^q#tQgwH;|1PsmDlo}?piNi7S8wPrZ+ z&L_YjZe8Pu@EO1>B4*#r=Pd1gaN7FSS7a$(d!=0O4q=G%lP&tMIT&!cdUMz}8GRDv z#e}E5XtAk)oaT9K|9j6~51n+fnV8;{t7I__**ABKJh~4+H1!i*)m|GZhy_tla6jLP zQ%PM23^R}LvN1>;di__&K9PPHWq2+)auk6*m+@;2w*ZwCO||%14bgzqg6Rkb{$gF7Bxm) z&eO4{u|lMV@PAyHYkJ-?W8pX;Y94hB-1Z0LZI^90&fT?;Wk2t6SSV>Bm_9zL{nB#d z+?_uf6%9)hMR=0fszys+eGQj^1`qgCP7n&quJLI{dSHA>KmVc%w>TmsJjp`H^nR!@ zL5u6Z?Z5|*9mdc0V$p6lB;Cd%T4cUu3|XRKH#&WWf9GV1*Apct5O}3TEGzD$hQS}- zwC(70Tc74tsuremud+Z8xegEZ@)`Ro-{_QF)b8DIu&;10F0>n~y}e(g)a>%wGH2J! zB=Yi<8DP|W#Bh4D>kJzB-erv>C5w(`t@Ygz%RsG=~m&oJ@`SrS-GxeCK~0 z4n%j_PT#uu>x8adP z(i@5tMeoFo&a8K>$ZiZ*3TvWl5X%^>hF!P%cu$4hHh&wxWI0n?J8eUfqf~MdRrzF; zC(S_4#Ve>`-YjJO3l~$Ac+k(p6j&(Eo}CzCTfz$rwh?{@9%pe6@1phB@7>8^g2g{FSOX*>^O~r_Y8< zyd$jx?AYZ%q^T7z`_t5Bs^_>7txiUab$gT8F5oyjKZ_987o(QBf)%u^+-R3h8Z{CY zs?xFiAc`8qJT%|d`<-xbO?j83FErS~g2oY|TghfEMtK{{`=f@I?GUy?`SSIQYQO9w zsO!OigXd1J&&K3H!`PA2Zu#!<_yF4Z;Y|Y)C_Co{Vz>R|X^=e}z<+M9y!QObi>D|p zm}zKj4;%ViJ7}Th+I{K9z8~G?io+E;_aw{O?Wzp{X- zaG}<)3mud-ygc!~Zm)wxo4$UJOV3c*g7-<`){kZ;21k-#S=M`U44%!fiVMmGl@Z|| zUX9p~sEzR5swlDZ4XT=YF!hA#R8=+4*o3S6TSLiv|Ca8C{A{>d7z#X5Ow#50IR?Aw z+0_EI6oy1vVSeB_liK#p=@~oO`saml^OIrX<|qBn`xyAnN@l9G;~Wcr+^?FiW!Dzk ztyS){Y$K|EDPlsA@lo;~w4D-lQwWlVnQ)3si=cUml7#Dzt;hN$t0K(r*$7Xqr?vO# zl&E;Yz1GH--?G}-gfd9{Ae-Q!--n(DPY~~C`k84hv^>@MNfdeu?X&i3Zum%#`N!9O zk-ah`)BuU|E2XV~ZEV zXU^4Aj}GpSxM)48w7N$eGS{>{;DNubv_WdOs^y04IS-dvQ89)Ks9Xif7s&eVLNMy~ zyf_IK-TC@3_@Oc;4A&#J{rmZRwTOMmr{R^XdNoP zmV4YA{$|=J(&yF6G-<&~7avUu95TyP*4Pil-9Efqc#snZGA=@73+4uQD$4|X8;I&O z(|<)cT<+OSGC1NU>BG?Mao0tBKyT9db}S1?-9uTHY$rc{zXJy5V}_!(8Eh06KraS9E~$i8tk4O<`;tCqe=`Q#?Aq3(IZIz@_@g zRrrm+=S5U$JY6{UO3l>fB=mYa`fKah_s`pBbz zS#>^Wb6lIt9Vn|hG9M;&e|jT{_)cc?WV6+WuVeapVXy68=TNxDwEH%Zl8W@i@0}UT zD6CHBJUI)#$IBcTY$?}xdulXPWJu8f-uR>yKlp;mG6@i&Bk4%K_1>ErE(T#LevNMr z4DuNldvmsOPy=aRaU0CXazkbFuP^hmEovG}K{WtjHUEj3&FTH?eN?M6vKQ}5Vv_bpKc zWeVi%)q`}+TXv?p=d}KW;dsWJB7+RTi-bneO145V231Tu3U6;E6cLbM6(`6PoCNG_ z^Js}&A36eo1cr>!dvv9B=lZkKl*eftcP(q@v(U!gax0Jbhlx9L8GZP_qh_9>?$gx4 zpt$UuqB|KtA0x>U=QmM(m%GO-(xCMM8!L6zHdJFfr@>3E-}Octb_srOPe?I!{OTV& z+!9WYot9!WqUg-kd&J54Nl7H*Dkt(*;e>=02H_klC%Zyn7oqpN`V?fUwNm zyWSPN-w2LW;=MD-zI*0PhOJmK1e2n?#FW|6v+{f64`zzlBzYpV< zVx-0kt0x3$#fj1^ayw+)DYy8}u*+4fBijcLZn7sG?%U%ummC>m*UTr9_ND?ul}6_` z1JxoWqY&V{OQH)1nQSne#uoF%sH#JOWXK)$S0=a*UsfzsXlAoom_VGTiDgB$ZMfiL z!cUTGzN5bc$G+cW*&`y22>WEOqC&c(>fNdF0ERAIkZzm(u5i@UAYG}bka|0BpQ6Ni z?GZ2e<9dGwUTDQFjQt@PQO%inHcP)0z`a^t4}H=WLp+s3#)*W+0fb?jBb!y5S>omX zF1aC*jA4=@8dW6Xu!))_8R?Eoq?B#skYtL(nx|;ep9n9bBZjRb$Ch|;0n89mp*>YX zw{7~AXt>hSXl=Zp!pC_4M*{r3m)!+sox(@(*ydetK)pP}WncB>Nv8RZv1seAF4TxR z3PyRQJV-C2Btjph{we3$hx1)fDu!T zfC8I1_T@d9Nz-3bANio12pVkKWRos@^i_SH7m3}TsZ+X_(Jznex16Lz4V61n>lkTm z@OG!0ILoiIQ6R zUDAB}rNq_sh}|MXPU`#woN^6bJC9NwaE^bIPd-|_+XJ33E_65O!@+n`Nzk(}P^8W& z(c;8wUftc_YOJ>$%H90f;sQFV(wOp$2uMlDf8*cvwl4`O&-p0{NngqlmVagG(i}@q zL^%Oh&kUHdo~u}_PyV>|&T=(B-LPwQNLySEfI)Y#!d0bAlP3JA$dh)fyCr zC0BFz+(wcdL#b6*)sSg0vFEovcWGzLUafF^4F6=wQ9pyNbi4LVYvu{azLX5qvU*HB zcbj1J`z5PvOA8Y1)i}t#wIhQ)-jLg|N==&#uB}QS?u`yOBK&UC0v&x zYQMFVm3c?FJ*2bok{Sy3?&d59`T68G8#_1{I3~*K7t)n3OC{~t5O&IeYgMG*C#xI8 zeZeY9Q)!Z)N<_A8GV&qyf0Py{JeEAPhz@?Ock`XxPbQD7JO{PkZ}UVFo^s7>z1CHR zsUZ_!&PSdA48t8&Xsxy5`Ndh%5t5P z4qHwUs;LlL6*#crl?8gYMqlXHzpqx#a>^k_IK$gnRX%5ipG4+hLwXM!+%pnTtL~3i553 z^DZYLH3IU_3+qfXE0t-<)bzhnlZ`KUV*%E7BF#K_A;{B2@lO8@ThtoK&mz$Ss;TXynnQ ze_IO{!+>@9aZPBSf;Us+{+Gm#J0WVal(ciU*CIk4u+_*%Btts3|4&ucVI7xatL?tZ2!K`5DZiBP zcxQ>aclZB@P2U}gSD{=|0pJt2g3Jj4MYhKna+Ybe_0IoOl_5?PZ*ZRNR&SxHP>u)X z1Pvxxm_Oyo&I~egF@Cf`PxF5g4u=f+72&ao~I+@Ua-87^4j}LRcp6HI;&s z3VCLli+dh4g`2}gh0Fq3|k|Er=35F?x)(q zE&n?yA#Olf4l`AP83^#MEcRlbtAJVRC?xJD@SP~_$>?N5>0e!c{2U{_r4)wEj0?Ui z`#A!btp;FNUm2xZKHt64wZAjm5VD-%;GA~5-aK?c`0VrnH{ef2tc+w`YD4s@vOF7w zgO=BShlUC_PBP^89GY%>%G_!}X)wGa2hjiXmV^9J>k@UelIkhRyGMk8^1sPvkgBsb|`tVdk%!YUKZnZ>_?-P`v8KeD@XujMTng@^Zmy3g^zHg{g_%bypJ--TKli zpBG^NkA7&WK?(hrSw1(uFJ{?;sp{>~Z;tK4D^HTF-U;st%ui0+pH0&99-t_CZfCsj} zBy;C5&4dqxIf=byybTt z5)~KUnR+t!NZ@R($K~h1imjiguV3y`b!fI_%F0UUl$^EoUSZ+u^2aP#kj}AvRp-SX zZbn97-t9E@$B2x@3U!HQBpXTK`?Vjp;*$cetYSBVGgq{WC>xcYNatzio~v8u1+xzx z#`GHQqO31?IuY292${F+Y(EHkGsX!sO_nQKI_M9J&EvBJXMJ+m4yXNoLqawhhnE(ttv2(h!+H^nT)##93x=mvkQ{v^JfnfkX9)3E!K!U8~T+Q`#_dY zZ$ClrRDx+icWYokolVYBVAcG~#L5qoc-_Fo6|DL+2s3`*1cukXsQ(rR5Zh{}kY$3a zFp4h;ygz9FhAn89Pk#;wgK4u~c&k%971|%IuljX^t`g53SUh6iKuU;UkhhV)2Bh8D3R)$X!SDW1#e>XEu^In8(ya>cL~aD4 zv_-Vz>jR68us|+h?`1s_;N!C22z9eoe9`W5o~Dxmh^+tvKC9sx;JEwc#_=}-yN&H^ z)6KF(?f_u;Y~Pb>yN5|^;k%frzZplP++iqwtK!b6z~ItPz%8N<5Yr1-@3!JP-=VYF z^taop7R$2&x%IpP=kSSP$3_ac@&Duq;k1>85?ag!1NKg8Zeu^tiWcfY};TdH+K^{-?21@b%$eN;#e0 z{P1;eOEW8OVEI$L%)!jaLS`V&GxKlt!H7HNB@}gf@zN%U{MVV>{Tr@Z=cUxZqiyyq zg`;>j8@F=J(-;4CQ8elvf}yOo$tVJ&{=7cK*3YLA$Rb0NZS>ph9lm6{iuH+iqP=J+ zC~x>R=q#~(ekUzITt@cB^K4Ljr^5OO&1G!M|Ku?lhP&^+EXWU=E#r#05b)u1o&(Q3 zDrexo9>ZKj8gT}G4=p^n3`=P0GiZMR#R#3lr%uY*My``ym5TgZF)#S7zOM84D!>cG z^<0^oKwmqaNd1?ALPBEv-^zk5```>zt zP+(~``UnCiv%^ph@8!UVCMbIREy6iQg=SV45r6c*sgO`R)GTXLr>8L-_33SNBDg2~ zcz7_Jp_JFT70rQT{+|?-px(PlEv6i{CWoHZA)V2nY8=|+>!1XFQa)dm-_*Vk6%wk3 z41?bv4#I0iVCtVctL|t`8q?!9S3{Kg!(-4_h%g6Npcg|7!i(&?!7O^do64}i|Ja%! zi}eh<|A$b4=F_z0bi#k4!0aG^>sw2OB>r4!I%GveVD&TImg)|pydMV$|66-6S?DOD zCChD5uWhWa+y$tzZ|RFuC?K{v|DW8!K=Rc2eYGtnI=CGtG)tx@`!)f%8*|!8A<5g?!MOa@!x+ zuADMT$&Io8TNC(ID3a)0Edqd1klrA94P?bAg`GSb;ZnFF=#=ws87Nq{2Jjr(QmTT| zG9-=qOopOIs~rZJPzjwo>gn#s{o8DnC}6nHOs}krLgK?RTh(ShCQZJ+sWaM$|ZvSuA%k*-Ub*PA-VBg0Fem>SZn zA0B-DqqTve&bFaks26UvN>(vgBJ&$E{u1X+RYf&jfn zPyW#h3>=09hQ~V}ZN!qzk5>x0d;qel&u+vQ^%f?2{;N6{Me1{4BoC5uO{cHWVs0Xr zTnKnpetamPnA`g+!f4D4oMlA3%+LsD)l!!4bGPqe9Nn-OSqenKZU2A!KE(iZ=qX%km*u4y11ik7}B83cLpkvcIGGUjmSon-QCB z(bir_Xin#>191bJuUXX+v^zV`&NnWf`Bz7q;Fxf~&6VowS*ZkcK$Wi=7w4K1G9^Il zuMiVByh?^or%-NGUtn1@M80R@d&|_zvHptxC4g`u#9+fNJ#9mmygtv!!3rUViKgA{ zGd7O`*#73Rh3vET+-X30{H!HH`Qcfs=5Gfs5cyjAMw`*wMdgT2i zK4zHf=KQX3^TpJUdk(+kT7)AU9X4Nk$1FFud(bk{Vs&~3i^-z~1J>mi%U;!tOZxiU z-r*rg#?PW)Dnv|~_yq(qEY3csS!+-QBUhGZgcGuBUZ)FnYc2agck<^Oe?g%9%xUH!#5RF}c+~-2}A~Z1{Zzb_)zz!q+;R379&o9ln{g`h3jX;WPvM z4u{~oTJQ>FHGim2{|5{Q?JzQJ|G}+SbgX_Uzj~Qn$v~)6&-e>SDpJv=`haBB!OKIxlMr@$IYQXC$CVl%U0v8H3p?gNWlJ?Vf&JmQ^WCsb<>>pHVpRlAl5gr9i+qAWqbLeeEF|xo012SMJzP9OQaT1cS9* z5th3yL19~>hEssE4CaQB*S#&}89C|hk78CKRWuk;KYa$0A)uZd_m|BCqv(WYuI(hR zKL_L9@~gH4Mp0c3_4aYg=AXGO4mgItTwRTB@sjIyPkgU}`bZ8&D{LcLyaD2XOvwqH zs#8lipmxf$e*A#E$w>O@C2$@kCiHv9W;W zvmpnfd_7?K9lbzbcq=-LINeN^1v5m_u<>B?#bO`Hy_iCQU6g#50@tmXvNUNGop0kEZ1ZTT?n zV&n0FMEPu{bpLCx9&9oUU|a7R`TdgQrF54-^_r*QOz>!U{c?i?tZha&Qv~=<{EqgH zd}9encuBlc<_L9;26Zfkdi0%p6r(M=M=?#j$ONb}*GqXv9F8AU12K14Anebr*G<-n zAWdfR#3R(ga`M`{GO4qH8y;3aNTkHx!Sf$#(ef2IqT;XgIDiC8*XsBC;~stuaL=)J z3sl#9cOJp!TgYJ<6;PEKHc`Y!iQlafn#cS?)VJ{?%!~eDv-t@>T`g&F)ZMf8SXCN3lKktlXAK0Ya2~ zBJ%95BUmqK4U0)sEwNw)qbEqu!Z*|38_E{gX+S7P5#5RnmVkMKIgf}ai$n1TKZ`o1SY~37sSoFV&ue}X@{eq~x}w*sa%sI8BO1!% zXT38|s?Dx)8#jI>s^{*%b^paHj|xIaTP_1!A6s3^n=)a3^pg(BlKf*4xcMH~t5}Y& zvDxM>-kUi2!Y}e#(VGH&&ccM*p|1n;qKot#L;~Vk-rbliuXFG*WnZ_2n<3hZ?yM?S zgK48;TkrH+RSL^o6D2J}e}M$s!)YcADMkVV97!TJ>BB*-w~3Q;1Ff?Aw?V!9#?as_90c(_r-EDj=Y zAlE5joPJ}?UdJ;r9h;G^Nbhc*K4#U;{=0Wn-X_C+?~D9bbio@mK0p+_emEB{aBj?4 ztGsz(pPliq?&j?r@ig!2gnBpvEfble8f<83dAmn#c9VVfQlG4WbR>dG_B9+n^cUew zPUTG5L?ri%ksm%ITW4Xo!_MV=Gca# z^>SD40b|7|p9$(wD2Bd-E)DATgUh|Tgv6n}N1iI$8w3bo&&s0S($uMGt*!N?vi<1I z*M*JS6i65x$vdTbSfyh)_3&zEsBN(1#T(>S`+ez9jN>M+D}3XC0J=QD>4Z$0lwZGe{zqe5aR2;1No`Qdh^cNc?afE+?_zlr)hd%0ZCBhsh)7->=3$q zUx9@f`BCTPQ!~FPzvK03?f__?CkpIV zfLpVhIGoL6V+RY*$gp?dhwMA6-?;a(Wc|JtywUl2@3Nb`<iob(@S;yr}qU(B<#_te?O0Y#Mxn=jZeJ=S(C(=>k9%Go%+>* zg{8N(iR&_t>P{rBEXK&$oJHW|zuucKTzeJ`IHl$H)({TJ1a608l8_ejIqEokD#pe; zOJ06U9tez303f~7_c>ME(qcx9F z-HP5Tww;t#^n>*R7z97Cv-sM;n2P-%-R z-<%DZ$L8W#%N6Wp$T-nr(q@l58MHUE1tZ1r-a0GxwKCFfi5yPAO5 zejLw9RfHd+&3JaqIN-D+fLibOue3^?0Jd_q0ZB5roTNVt#-c+;O~zKP^Wt#kvgg*P zY{vi=N|({?wx<{#2H^P&xBUEK?(C;zuLGD^-K(r1u-Lr|_pP2yB!CFe$%vk;DwFxj z=Dn&`RSjRPRpyV`ta$YF5du!WapS)%%WHG<{nlsX-Y4bW)G%l4f0?j)xOTNlZ0`oae(o|CnS)4%$(tOXN>gP2SB< z(9&I8Wgj_+)%eqT@Lb`=y7!f~&BTt)o6i_!)&%fe30KV9v(0fJSWY^)cG?+!E@2(d z4S+@0vF35}jE?h_diU-o9^3|1J;p@YMxxOxPk?K+#ur2aHe;;f4bR0Vs8mhwHe`O+@1#(D6Ur zDK^wR!HqnXa*LNJ#YKJ<+MZaeGpOza>9$>?=??1WAO>U;czLj#Rc9B z3^l>&KJs>{pL&CJ_mRg%-{m-)`(J7(g+!aiW=MiKqhwCs@$2NAUf_{T?mdi?uSqDz zKK%O=1SRVP|IKn!^H+7)kSQ6n20woLl+Jr?-D+ga9RM%B3$j7=FCTvVi2sVZEm2s1 z)3VGr$(>Gy&p7eHsb^5vg4c@>HD+<=sZNrhzRqb>>yt_W8*Y);5_ylHjDYTp=hGGE z`4{uE#yyq7Z$E(W_&O;)Vn@Gisa(r`xk`~k6rlf8Y;};|@R~ZbkGG;C>OZYa^Xt>T zSMObtnk6O02O^}qF3 zq%A1CM_!MWNuokM%gOXc6cEpNPZIL7KhZzC@t=9N%K6LH@#+KvzT3uU?oJ%_dcD@W zuK_~!&vMhsRjAdiU2!Ys;nOo5pNALI^bf+0`B6fG!2?=iX6!zG6t_DuT=$=8M;JG==LPlCiiWv7l~yJ_0e88y!X`nEJS|PRO~o%> z|Ne_rc%{py#=_1^vSB%$su6mvQ-Tw7N1 zoy7=!NTj?&cDe3a?=2<_WBpv3S?WoNi`IB_>nqq#!;re#_Ec~gNf`-TRyu`dCPP7s zC#?o_6SC?P%hhp-pQ%S<1T6bW)e84NW5FQbP4r-iJGl9wxIQ96=P!Qp!~ z=^S7&+17|3go*Nze7Px`Ac>WeTc}TygFG*kE!WKFW8XP)uHGXUREzKhPP#ID>J9Es z;(xiHUyhq@W0z(*f50p+*h$ek@6?gBr)4U(q|UP$>cMy>pK2P@&nLF}sM9M>UuKag8NksX`zyyL(aY4efo7OS5Z*`faPq_Ii}i3p3i7S z!)oj6+4u3PxpL7jx&fapU3SaIv$|U+mWCGF(sH&xdIurpw_@o!Pfm$**~Znrq5*~P zKqnsO91nJ>wjmyZxvr4*WaHrm-t%QXCaoIDMHX168f zj+->lAs_1jR4mQX$fPCOE@Yl<>^}QLrp9ySNEBA3rnvzAadu9#9zA;HN}wc*kX%0P7k-pSLMN5jWp5>7$-?JUno@Ba2P-7~LU7 zPQE3gIKAIc;A@1Mo=(rRyWDCxvCfs8jbXlPA-KH#;r#&5Hy*e6Ubf!>p=*97)Oqxo zE3>+#JV6nAL)(WWe@k?o(4hH@Brn(aA_Gj>{Cl{_r7WbWl%-xeR~n^|=hIoy=of5# z5#%--Er*>B<~@GUtA)C-2^M@_I_W2O6)-?`kGp8+WfUqUci@vkBFkS9Sw)`K z^65|8Cv2`r4AET3%#2ipv}-b+-^$XD18k1K6~)qkN`wL&?;TCZEm*oQ6Vq75pBBEx zPXZe8tHfHb0#ce1KjeqV@O}hE@}*krg{pS(gRg6|xv(%f0XuqirL#BuSH&SwA<3zl0WFS5U zID1bB%(x)~cTxL>FDx!T_E}s^N}c`XhYKv#y9+ZEL%+*WfLE%T<9=n=xhd{D-M;vXNAy?dp$OyUf|7!>!qQR z4}F-12h+?moq(L+C8p$$|45=W^UGf=17CyBK}I*qnvW~QsrfmLo}&I|Y54NXuwj5= z!p$lsw~}Vgfz=$M%ea90hJP#ijeMb>t|%`@hyQMGJzmL|G#AwQe($6bf6E(}buM@N z=shsm$&{pjZStUgPr*i`yQNjhD8M?W>v)A+!3G)C%)N1vps{XG#(gYBQWyN?QqFRn zj3Mrh1CYKJNTL$mD3Ys9lB9(6d+W}$+{%g`_P^~OQQz5MkaVneUyb9Dy|7Y$wQgm3 z#ftgcEt~pU+P-a2TEO+ducV5OS)hh(z3)fHNOzENj*DkokNS%gX@k9t5wI}=-bR%B zK$Y(|Q3uJ}wNs*6@6_)u%$f|~HM$Vh$RKsK&WQbXR(%Gw;_*ndqp4K9DKZFHn!Huo zTFg#Y)4P9$mf-htkqRDhPM7BT8flspD~@yokyXoF0U*~)s@n3am{ZW?8NOltp;#9{ zQ(KeAJ&fu<=Vjn}+b$k{Ncm!+Q(fFmh5*iRun&fLnB_wq=Bi{*RHMnEg0yx0%6EmQ z_9R7-e2u~9OFhn(>+C5^3q$y#H!&yJ^X3;z@dKj#aBF9Bk<zDN$<(0 zErc%1(3}vq!fx-=Ky!-$r)uu2=kb9Da=0?bQd}4O0M3B)Z5B4%!pQvf^a~kR(?Z!L z=Cbef>=P};^NP6bI!SzdI0`vM0zRWIPd@5oJR@U${%s)e{$b&33X<`iNM+PcHm)oQ z4*mU)$i88vjk(p52w)QK8J@WheAvfd(c?d=5-4D*_N4?cMLRn4_b#hj)}l3mBlzuW zRb|`>Ez^QaAND74--r*8!ND?qh^GF>fjn|s>8KJ)wMppg6e2rtO>?>n*FKWs*cKkW z2Rm-!VH7AflzDm9lZG!Tv!e13uNBzKrq9He2^%OEAHtX5Qbo2U072wFbV6$P1;S`Dn57aY2MY)Z;nb$gL4D6MK< zr$>jCy&CeqC!z6cXVSENYIYL+z+>9-i^VWwlAuIkYX+_llQ8rUzOrrkg#B#wTZy&^ z7piRD$BG524p6jAIN3M>291MRDxnA436`e|UVl~JtQM$tP;CAA|2A@O7H{Rjh1JY=#I$gU9`$Lzy6{WscUsDYazlW&@ ze4N;g7qTT4SUy~x`JulvpO%!fs*$;70gS0kF0haLTz+cPXtL9M0wd(5b2w60gJ!G# zXSEBXicOi&G)houz$|q8>NM;V!+?CUA=e4{PPvei zUybz@%i0Y3p@%r<#?iajjWJ7u3q~Q%L*X1?hksW1`{D3JLRPf-{I{O@tS@+c<4q%qUYDr= z*8V}U#BVfIa&3~QdFX4XJTK;P_H)3w9y;#PmfwHy{Y1woRNq?HAV{tlulbF~)O|V$ z7M;?D{9qOR%`_`bV0u=3#3;skEY1H|S>=Iy*ppealz9>r5982r2S>$GV11z0v966j z7sx34>T-8AjV3X(e2McqxSv!~jbET&V3xlX=}RuBq?A0y^v0@%_+J8d$;U?>0QE}+ zZFCG4608-1-jU{^lVE9PasNshz8%wWjCO+oNfi^()U#Y?{U2UhY&$NA;!;F&29}mC-gcfjTWl`m4ugChgEe9FutqpH{K%kVp~i~MTai;lVgXh*S~j6 z{LrEqqB0qc1NVTS7vPYV=2xf-XKN?k(QOWJzT7Ox^-P{#%%)u}k~z85Dv|w$DqYG= zO8Ato8t@O&Red;Dkd)MepYE6;(vOCHce*h!5~jpk{?%fT%48q|d?x;5AJ<=IdS!*E zCxu+^AA_vpCjDCqnPY;rw9x{?hJ(%bmK;NLUn4=Z;abcl`z+J0O)|as#_id!M2`97 zq^Rb^dvfU&>V+m=E5-J@c3g-7&*Q9#@yD65O3-@}UA$Lsm(ubi5ga}`VdGlI zQi%E0S)x@}w;aO&LjQNXKEtM0N>bwL?0nkz7Or~x<5PI#Xu0E3LN7j?)UsuX z#9UU4oSa&RI`AayiEhQ}CYEr25c?4IIXoAeH0pUBB>JupujXy}fL4n`{hDPq|F2SP z_-B`8;IX!t+o~G(r7?Dp%DO8qZHNegT+3sEl}Wv6zI{hP?zcFEy8{yjN3+&w0k8f2 zKRLt?^l5}@0zAQuwW7BY)qeAB2-kRe4^*D3ua>Pi^jN(en)x5Nk-#6vCjIR5QcqyK zkI*>W{%lHAmr45yq2~)KX?v6D#Kb3WjCMG{`fBN9o6}hrW&Q{}OrQQmVR}xz9;zKT zop_T}rKAH!tksW<7V!E$1y1Te)n{8Lyc2toEbGfCrM?e}56J!FrEbM{)B8t8#0*+* z6V`74#>Dc#!BuD>iV6x+pq3o$HrBu0!_|fVERJQQ;nzx9*jr|qNsamAt;BmcLcg|r z?6%i4R$X7{3;c5A9y`&d@CHbay{qg)fG@QWQBV|)2P`En`8g2Z_J8bu7$b>`W(hoh z>3`)C_mt^2`pvlBmd{%3EU?UM@X)LYIOa0k+0V|~I$GVjA=P43PZ@>HGuAWPBy1kE zK6+7X&1+WX$M2S6;P&*fJS77B{2++r&-b~bFgby~dFw6dnQ+0)0{F>S27l&$;jOu`a&=FCK`*DYDM3Z)BekM!zCiG;0AD^NT;&j_@uR09uL8x zuBA-?--WUcK6*bebVPYf(&f@MH+cMt`}Q()yo5+HU|AM!ad94-uxcGdell@hwAAoZ z@+pAb=6a2eTs?=7&@(Q_fz}FxqfMRv(hJMmTPyqX8UUBT^aK;g*XTU3p7p=_hG@E}=VhvxRG$a}bJEJpf8`OR zBz#;$-7lg+Qi#+?i${9)4vR}J{)n7l7_R#5@fMC|9!pHr>Q$lNB@47u?W2wS&a^7B zYoex69WWYWD?b4c7ces!^+(dYx^88CCq zra+Hx0{p;@x=WvjI5f6R(|)Krdfr!tx0z8DPp_MQj$6vQ6VG576v<@kG3LJ}$1d%E zGt=A{&uoTbVF(%H|M3yB3tyb z4`4E8HC{#?AZ)j+3Uc39ER1eFo;wxQ?l}iuoLiyiVFItEAt&1sJbZfWcdJvTYKY{& ztx~O$w$d@|Qf5caUISh-2EGQ0n$n)Cap)T_O;*Qv)P@!d8I6aQNQb`!IBE-cJ$Iyi zQu9ZC?(!+a(t8?e{iz#23R0;8SMuGbvXD8mtw#0W)X5sfFQD@BNsL znCVM^1S=frc4QW>oKG9A#ossyZ%DecDRf_(X!?5H*Rt}VR*mYncf}LeZ9l3YoNG;A zyZUAUA|-j-w%ynKbpO_}UPA}X*}Kp%&g{eK%huh@Do?ELzoN~PwX{5oq+x0oZJv#* zJg^A1^71%`hu8yD5mSMfe(A?cF=19cVA~~ZOMHt&1Vci#PJVStEgpuV9+p=0yA6EV zkOs~Dv+?tI_v6;fCknw=x&V1(>RCagtQ?+bv=%(89>2=+%Q1cH(@1H$WcC}p_sor? zfj2mA?hTyh{S4&}yB+iX%0M*WnH}FZsApQhc8&V-j{ z90hQXIYj;|R# zzsyCCDk6=s5s-5XtS?D_ZLs>@bVL+N!%1~34E|9#^PEvZcBY!Z?{&LiojKVf(~PMj z#tCqz-w_i31u`5EZgX@1e$jW?7t{Mdc#%Y&%x7681(=8rC@u2?yo}o8{VyqDDnxva z2?i+fYRLGj<|X)Y+zOln6FdO>Ubz1La*=)P04O9@gVOIwH(Ka_+Q&QW)0PaV{BSlk zZ>LMd)4~B)Qv&s3_=7R^ggwI^Yg>WT;tXo|Z+P*HvmEFSaT>Q4s+2f2UnaukqIb*k z#Q;pYc0yU?>-x7%+_kIh5Z!Ak;NQ}8Ti?s#xN9Xdpn4y&&|-7a)T=gYD%@W9?uvz3 z0fjLOdHG9rx!ZQXLf-y7PK!BZrq?A;HJfdtRO9;APT2cuVZ`q#LMjX$yW1MD|B&Y@ zjp1^~la^k)Gcmbj9In@*z#{ZuI_a=+Il*;j(ZlkJJ%y@L`E4QkUzozpC%nJp5l!#p z)Vfeo)0a&WB<&+k4_=t~4kvqyG@V16VLJ?u#}BHy_ImFTfdwmp8)} zd-)?D`9By@N8Ad-ob;wFQ3zZyit^-&A|nP!8Sf>;%1qk4(Yf4W#)aM%I-@F7Ki6<9 zHex?#QPMCtyw|jeFN`2GPKm*=Esal!+a-22i_uY=PP$8tw_?b{wRj*l9L;n6XmCP& zH{JWS2^Vn{GmJAKyvtkm0Y5#B_i;=Du1KVh2!%5%T=MwVi!Q7xfY9~NekRH^i+>aX zElOwGfnF{+8VmdZiaKs z@p+#`^CZHK9jt3xq!hM`FmfACqf5<3Zi|?s)Gg!*^gaUrRUblgBX3KQKVs9{F-dLo zu5H@e`td|0_?Q!3!;bc*0eo&26O-WMc6mf98j`BW9hheIC5n~I6sNF%WTDd%?$LPG z^}+*A^$%e)BelmT$9*_8wp8gj6o|}fpS%I{B}czItiiX^b>YoeHT$3H#X^qjD&E@3 zOF5oxFs7%>k7BjvE^IMdb(q+KB}6Q94(7At91-=#F_Y!hHTy&m-uQ7B>tuPsEcWuh z(P5q=l`#OHGM&T{X8k0gf?u0F8jeTk66lM481z?siER`fj{era38^C)+JYVb>DEx$)dsUJgXYi*}aLIhYxK0XZllHRbmS6B+^9Q z!9@dFQcvP$SO@(nZ(Q%qyr&O1ZJG{vtzHu?3IKsI+dB*u$IG@aS}&s&yHE;YL88Yu zV6W@ql5mZ5jkwaNAWl23fA{;a>%(DG6PA|5x1`kxflY8^tIFu1Pr{zlSRbf7u;_fTzj#*=ZzLqNj4=p8=NeepE>&cZC~qjoDGVQ>xArfdaj;O?+F>h zzozFs6ll7+$WN2<0nB%%F#rPfQc~q*gPX*8YfCRI=%xToi-@C^f8LH88@Ve2xNi@A z+%^7hVpg-~;eR9s5s)hmhS+&7&I^2R3TVV}inr0Q{ioVLgdiJ9jS5j?FJJ=tvzJO6W$9~e5%jhL#GbF@neJ=wAU@A9rIJKcooT#Nd63c0<5_S8n~THft`WGrF5b4lQ#}KLE)m z-HohUnrqjw4d7-j-OeupRbK^U@uZ)IW4+UTIxK5@(q9Q8*aO9Kt4PDT*JV}o)L%!u zA3?g6uRb%>ctk`-Cz5eGd>qJgBKk9IQf>4&BO_nevEHk2LsqV5Qy)-Nz1Z zM;;*D#)`-)4jUpLX5hyYE`GYtQ7k3&rX?Zddv;3&@B7Dd3V_Ib+4Gu2M(Z{|efYb# zNN-$N@lA$Wrac8t?FH2Mx+r5DT&=*~?A_p6RA5*#|L+ItxqL_aZPwI1Aq6QMGBNyD z9Y{&1CGZAmv&?+|9KE~`MbSXiT(kC@z{&wiJ^h4vFt-^y!f!rDr*QJM011kf_kAue z|H1`t02UlQT=&5oX`RWZQ!6M77b0f+iWMG($a1OUw3YiUh`B_oQ#N#x$&>j}c~Q5T zUhRu6>H=p{ zua{15p~o*;n*&}Eg)ft+e1)qw$5cx{Z_zlbT!&K>JBo(5fm4fS2fU00jOsp}>P(#p z9o;{!UvsG&-+wGJTZx@(Yii!}ib>@F;S_%tN-S`t`BpSE5|dLz$T;1VE7GqEgKAQ4 zp2^wg5@qQqrzY?_ivrM!CB|WGg`Jn{Bgk#8kz8rTUBrlTPcB2P+jrDIGraBtmpxSV zwD~4w-i|#xO$jtj2dy*wu#@H6W;lKlfLrG8y|Z19e66*e47j>VUD*2HJ2%)& zSkXCInlF0FroYYVDn#9c5{rYdt;f`;S=H~0id~rLHNxI&c7QV&)3{Kh!}`8#e%ggn zvpD&TOpS)TA(rmX`cYI+42@1tjp1WyMW|T%p}iHclqFw8j%Vv>izxy$_dvvPD=M5TY_L{9WpJ1-8tBbFK_8x?;Gd;!n>f)e#-)WR?+jn6GtQ-Yhwkp*hvg`~$ zP;q}&3{XSJ4ey9!awxA*!@QRzKQ-d}6PQUOB}7*AdFUr6q^g|^D+a7y`z|?>gZBR% z5Z9{Dr~n5#I_yju-iAK}Mt$1!&FmE9*m2Q^DWpH1#v&lQ*E}6vMAYkU=1Iby_gh72 z1M7dBv|@XGT$LZC_{P!L@AOpKXDUIkKRBqQHGq8PSe|6W)JjLF#R#?C* z;&i;k?kJ_Kh(b;#s&!UUD~2Y?myJuL`wMa`0{RB66CWb@o*z1 z^Rp*zYvKc+i@+PBI<05q9Z!cd0PmU;y#`|sWSLyheAso`5v#J#RD|q%A1=YDHbr^a zGF;$tnWTf69V}3&r)FJ+XPtj@v8l0+fO!T(81I~j?D3g^kJp6+$K{`CUXvYf7=>6g zqzdiapDfWA8hYpQ^R_&cr1SC*LZe%?nzrQe&qY`tt**XZnnY0ykMB{|HJdCeL_$)qILpHj)7%$XX_WXv6Vrbdmeyc%6F$^6q21pVd##6R ze>_fiBA%4?$@)(}Kg1Ck@?& zod`em(3CwL1k|e%g2+Y+@2lvWdjPJBxJ5>NJ$0uD8CgCS&cze?#CbCdfdgH@N^er)wy(7FN$fS9FRR|FIHF?q}-u!(RX40LO<%h%* zJLWHV@Ibwn9e{H>=-JWVrIB-{;NGsfYTy{Z-`!4gF;|%eQtpjuVB06@?MoQ@2bV8= zKH~Y`U5UO7u{RroETzjF@8zHf|EqvwDuF9@n9TO-Q$)F5JLgcgSri`h{_{~$3LY5Y zc1D;9na|m8mr`d(Te^tAfd(vLSN8ET?=)!tRfWAP5k$hcxRHX!wsGs<_~94|En{Y0 zP8i?VEW?z6T!x>9ISxx2Jkss@E-qd|u^TUe& z1VUks?Y|T%Gc1cD7dg~uJO5L?4rYjOtr+YYQ^l2DCwxK*4MBoChzw{Bv->vIs)Cf1 z`JEa=YVyD3z5f73^rj%52uTmQL9=sx>@M%^#!$#u`zeB69)c~$Fg~b-!-d_`Qd$I+ za3Ia$h!4s#v?*feVArAFT>}tg^Ni(qYZpAYb`dQiB)~(l8OCcqH<`|7ZlZwy-$7rd=f$M7OqVSCrMRi;7@iXwT6h`r z(5)TDzz&&McY;RBbNX}FgCg^0u=D;uFMyvyni)QHL8Yd_ulP78zhKhqKPwb({r1VG|cbk78E!Y1(TF~ZS4);Ue0rp;Ez3;nX- z{SUlDj?9~(v5$J1q85yR+=d07o&E)}?TTQ{k8VKFd(Zj_R3{QD#U&c(dP^wiU^Bx7 zNV%J2o4OIXD{qMe>s03NAHb0`gRrTU@JhiBQCzr9@}&A-=FuDA>Z9id-5rnRu zTjyaeygaG1xh5C_iID}G=j74(%G2$wPajfgeep>1u~_$R3lbx3T9F{Gkz@fwr{7)w zoRPPz-!OcpQ9?qNH?=fN?O6Y6SA{^ZIl}76tU_GcT-nZ#i7u>lL`9uAJ2TzOJzz?z z{=`j!HWpg;O~|e2s={qNUd+~A9hT9$Rxh$%x>}lSzkaH&l0VcEJH-p+O^%07ilTSt z?|W0rLvm7R>OaPW?DGw+)5rYs&|R&kocqHt1)mTCdQ={tD6*kl#P<+Qj zAvV62%U%7#ONU1vZ;BJZ@((ln%K*%MU~RFJws*jm_3~v&tl<^8)_QHf{ruc;sXxaI#f4U%E+;8%HVkWQ9Ehtut%LK||ue-hsdL_Q1%f z=whF4axwD{QSmgXu$~cvzuPm&cU}m`vMF z;JC*fB+A-8gBoS8Dmlx_YL`2cv}XCN$4Qh7Q@snG9WC(_iz)H90m?xMEf$uU*OBw- zVKk0B>QTpbdEQR)l!#gAEiw0Y-oJVC&gH&5k1(3+MP>J?{S?U5l;6ESI=z>uj5682 zMK>t>xKe;p7^OHwvmWICeZ(n81FLlZe(v|M!ImI5D|ztD&}w+as)W$|rw=BrGC-D{ zDqB~5usU;JN>q4<$Ao|O3ZtsmY?}W4jYxUq#s^d6$?%!(j$%S-M8qE;r2+l0>PpY! z4*cb(jU)GJcz=KAxas=Sf5>8!^Du^heYNap$27Vigdn4-@<^xD5BNGj=ai(0J1pq@ zS79+9Z9X>O?rq?VDjeX`r7=ySX!4b}pLb!u)l1f$Op@{1f1`kr9~TaH>gP!M*R5D) zb3P%fULey*l%c-ZS}RPV%3zd@Th_V

ev$zU+4x6EXMSw!Pr5zC>v89P=F&O%7#T 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%0A>9LdZ9oAsE@>)QlAl$t0#bRiK)~ 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%0AIs&*bb=ae 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

nLE=PT9q$0d4OCl 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 From 08a6a2b6dc4a62b0a65df61fa05ac98558c386fb Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 21 Oct 2023 13:04:33 +0200 Subject: [PATCH 33/78] Fix potential crashes on macOS due to missing window inside view event --- Source/Engine/Platform/Mac/MacWindow.cpp | 29 ++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/Source/Engine/Platform/Mac/MacWindow.cpp b/Source/Engine/Platform/Mac/MacWindow.cpp index 000accbfa..3129ef883 100644 --- a/Source/Engine/Platform/Mac/MacWindow.cpp +++ b/Source/Engine/Platform/Mac/MacWindow.cpp @@ -4,6 +4,7 @@ #include "../Window.h" #include "Engine/Platform/Apple/AppleUtils.h" +#include "Engine/Platform/WindowsManager.h" #include "Engine/Platform/IGuiData.h" #include "Engine/Core/Log.h" #include "Engine/Input/Input.h" @@ -14,6 +15,14 @@ #include #include +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 +277,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); } @@ -375,6 +387,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 +418,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 +426,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 +452,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,6 +467,7 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r) - (void)mouseMoved:(NSEvent*)event { + if (IsWindowInvalid(Window)) return; Float2 mousePos = GetMousePosition(Window, event); if (!Window->IsMouseTracking() && !IsMouseOver) return; @@ -459,18 +476,21 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r) - (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); MouseButton mouseButton = MouseButton::Left; if ([event clickCount] == 2) @@ -486,6 +506,7 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r) - (void)mouseUp:(NSEvent*)event { + if (IsWindowInvalid(Window)) return; Float2 mousePos = GetMousePosition(Window, event); MouseButton mouseButton = MouseButton::Left; Input::Mouse->OnMouseUp(Window->ClientToScreen(mousePos), mouseButton, Window); @@ -493,6 +514,7 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r) - (void)rightMouseDown:(NSEvent*)event { + if (IsWindowInvalid(Window)) return; Float2 mousePos = GetMousePosition(Window, event); MouseButton mouseButton = MouseButton::Right; if ([event clickCount] == 2) @@ -508,6 +530,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 +538,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 +568,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 +590,7 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r) - (NSDragOperation)draggingEntered:(id)sender { + if (IsWindowInvalid(Window)) return NSDragOperationNone; Float2 mousePos; MacDropData dropData; GetDragDropData(Window, sender, mousePos, dropData); @@ -580,6 +606,7 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r) - (NSDragOperation)draggingUpdated:(id)sender { + if (IsWindowInvalid(Window)) return NSDragOperationNone; Float2 mousePos; MacDropData dropData; GetDragDropData(Window, sender, mousePos, dropData); @@ -590,6 +617,7 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r) - (BOOL)performDragOperation:(id)sender { + if (IsWindowInvalid(Window)) return NO; Float2 mousePos; MacDropData dropData; GetDragDropData(Window, sender, mousePos, dropData); @@ -600,6 +628,7 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r) - (void)draggingExited:(id)sender { + if (IsWindowInvalid(Window)) return; Window->OnDragLeave(); } From dcbc917b7defb603743b97757bfcad73984b7363 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 21 Oct 2023 15:13:36 +0200 Subject: [PATCH 34/78] Add local util script to ignored on macOS --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 54907892f..b7e11e554 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ Source/*.csproj /Package_*/ !Source/Engine/Debug /Source/Platforms/Editor/Linux/Mono/etc/mono/registry +PackageEditor_Cert.command PackageEditor_Cert.bat PackagePlatforms_Cert.bat From 21f2e59d12ad12af3e974be563d4cde7ec5fe8a1 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 21 Oct 2023 15:36:38 +0200 Subject: [PATCH 35/78] Add drag&drop support to macOS --- Source/Engine/Platform/Mac/MacWindow.cpp | 103 ++++++++++++++++++++++- Source/Engine/Platform/Mac/MacWindow.h | 8 +- 2 files changed, 107 insertions(+), 4 deletions(-) diff --git a/Source/Engine/Platform/Mac/MacWindow.cpp b/Source/Engine/Platform/Mac/MacWindow.cpp index 3129ef883..08a4e0d2f 100644 --- a/Source/Engine/Platform/Mac/MacWindow.cpp +++ b/Source/Engine/Platform/Mac/MacWindow.cpp @@ -6,6 +6,12 @@ #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" @@ -15,6 +21,29 @@ #include #include +#if USE_EDITOR +// Data for drawing window while doing drag&drop on Mac (engine is paused during platform tick) +CriticalSection MacDragLocker; +NSDraggingSession* MacDragSession = nullptr; +class DoDragDropJob* MacDragJob = nullptr; + +class DoDragDropJob : public ThreadPoolTask +{ +public: + int64 ExitFlag = 0; + + bool Run() override + { + while (Platform::AtomicRead(&ExitFlag) == 0) + { + Engine::OnDraw(); + Platform::Sleep(20); + } + return false; + } +}; +#endif + inline bool IsWindowInvalid(Window* win) { WindowsManager::WindowsLocker.Lock(); @@ -323,7 +352,7 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r) @end -@interface MacViewImpl : NSView +@interface MacViewImpl : NSView { MacWindow* Window; NSTrackingArea* TrackingArea; @@ -632,6 +661,34 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r) 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) @@ -682,6 +739,7 @@ MacWindow::MacWindow(const CreateWindowSettings& settings) [window setAcceptsMouseMovedEvents:YES]; [window setDelegate:window]; _window = window; + _view = view; if (settings.AllowDragAndDrop) { [view registerForDraggedTypes:@[NSPasteboardTypeFileURL, NSPasteboardTypeString]]; @@ -705,6 +763,7 @@ MacWindow::~MacWindow() [window close]; [window release]; _window = nullptr; + _view = nullptr; } void MacWindow::CheckForResize(float width, float height) @@ -938,8 +997,46 @@ void MacWindow::SetTitle(const StringView& title) DragDropEffect MacWindow::DoDragDrop(const StringView& data) { - // TODO: implement using beginDraggingSession and NSDraggingSource - return DragDropEffect::None; + NSWindow* window = (NSWindow*)_window; + MacViewImpl* view = (MacViewImpl*)_view; + _dragText = data; + + // Create mouse drag event + NSEvent* event = [NSEvent + mouseEventWithType:NSEventTypeLeftMouseDragged + location:window.mouseLocationOutsideOfEventStream + modifierFlags:0 + timestamp:NSApp.currentEvent.timestamp + windowNumber:window.windowNumber + context:nil + eventNumber:0 + clickCount:1 + pressure:1.0]; + + // Create drag item + NSPasteboardItem* pasteItem = [NSPasteboardItem new]; + [pasteItem setDataProvider:view forTypes:[NSArray arrayWithObjects:NSPasteboardTypeString, nil]]; + NSDraggingItem* dragItem = [[NSDraggingItem alloc] initWithPasteboardWriter:pasteItem]; + [dragItem setDraggingFrame:NSMakeRect(event.locationInWindow.x, event.locationInWindow.y, 1, 1) contents:nil]; + + // Start dragging session + NSDraggingSession* draggingSession = [view beginDraggingSessionWithItems:[NSArray arrayWithObject:dragItem] event:event source:view]; + DragDropEffect result = DragDropEffect::None; + +#if USE_EDITOR + // Create background worker that will keep updating GUI (perform rendering) + MacDragLocker.Lock(); + ASSERT(!MacDragSession && !MacDragJob); + MacDragSession = draggingSession; + MacDragJob = New(); + Task::StartNew(MacDragJob); + MacDragLocker.Unlock(); + while (MacDragJob->GetState() == TaskState::Queued) + Platform::Sleep(1); + // TODO: maybe wait for the drag end to return result? +#endif + + return result; } void MacWindow::SetCursor(CursorType type) diff --git a/Source/Engine/Platform/Mac/MacWindow.h b/Source/Engine/Platform/Mac/MacWindow.h index 8850f80ba..7c3f091b4 100644 --- a/Source/Engine/Platform/Mac/MacWindow.h +++ b/Source/Engine/Platform/Mac/MacWindow.h @@ -14,8 +14,10 @@ class FLAXENGINE_API MacWindow : public WindowBase { private: - void* _window; + void* _window = nullptr; + void* _view = nullptr; bool _isMouseOver = false; + String _dragText; public: @@ -24,6 +26,10 @@ public: void CheckForResize(float width, float height); void SetIsMouseOver(bool value); + const String& GetDragText() const + { + return _dragText; + } public: From ad15c5b2fcecabec14fb7b31402d4b9ca42f2f94 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sat, 21 Oct 2023 13:36:39 -0500 Subject: [PATCH 36/78] Fix particles effect not being able to just call play if islooped is false. --- Source/Engine/Particles/ParticleEffect.h | 2 +- Source/Engine/Particles/Particles.cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Particles/ParticleEffect.h b/Source/Engine/Particles/ParticleEffect.h index 9e9792a4c..07d82d703 100644 --- a/Source/Engine/Particles/ParticleEffect.h +++ b/Source/Engine/Particles/ParticleEffect.h @@ -133,7 +133,7 @@ public: ///

/// The particle system instance that plays the particles simulation in the game. /// -API_CLASS(Attributes="ActorContextMenu(\"New/Visuals/Particle Effects\"), ActorToolbox(\"Visuals\")") +API_CLASS(Attributes="ActorContextMenu(\"New/Visuals/Particle Effect\"), ActorToolbox(\"Visuals\")") class FLAXENGINE_API ParticleEffect : public Actor { DECLARE_SCENE_OBJECT(ParticleEffect); diff --git a/Source/Engine/Particles/Particles.cpp b/Source/Engine/Particles/Particles.cpp index 5850b6736..c039f4ba6 100644 --- a/Source/Engine/Particles/Particles.cpp +++ b/Source/Engine/Particles/Particles.cpp @@ -1318,6 +1318,7 @@ void ParticlesSystem::Job(int32 index) emitterInstance.Buffer = nullptr; } } + effect->Stop(); return; } } From 7d9991999d583a2c0ed89ff15bf1c1aa8046d7c2 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sat, 21 Oct 2023 14:08:23 -0500 Subject: [PATCH 37/78] Better fix. --- Source/Engine/Particles/ParticleEffect.cpp | 4 ++++ Source/Engine/Particles/Particles.cpp | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Particles/ParticleEffect.cpp b/Source/Engine/Particles/ParticleEffect.cpp index 788a4263f..656a61991 100644 --- a/Source/Engine/Particles/ParticleEffect.cpp +++ b/Source/Engine/Particles/ParticleEffect.cpp @@ -283,6 +283,10 @@ void ParticleEffect::UpdateSimulation(bool singleFrame) void ParticleEffect::Play() { + // Reset simulation when play is called and particle system is time is complete - ex. if IsLooping is false and simulation is complete + if (!IsLooping && Instance.Time >= (float)ParticleSystem->DurationFrames / ParticleSystem->FramesPerSecond) + ResetSimulation(); + _isPlaying = true; } diff --git a/Source/Engine/Particles/Particles.cpp b/Source/Engine/Particles/Particles.cpp index c039f4ba6..15d484f8a 100644 --- a/Source/Engine/Particles/Particles.cpp +++ b/Source/Engine/Particles/Particles.cpp @@ -1318,7 +1318,8 @@ void ParticlesSystem::Job(int32 index) emitterInstance.Buffer = nullptr; } } - effect->Stop(); + // Set is playing to false because it is not playing anymore. + effect->Pause(); return; } } From 78aae0da5a18afdc8e528cfe59427b069b2cb501 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sat, 21 Oct 2023 17:22:02 -0500 Subject: [PATCH 38/78] Better handling stopping/resetting non-looping effect. --- Source/Engine/Particles/ParticleEffect.cpp | 8 +++----- Source/Engine/Particles/ParticleEffect.h | 1 + Source/Engine/Particles/Particles.cpp | 4 ++-- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/Source/Engine/Particles/ParticleEffect.cpp b/Source/Engine/Particles/ParticleEffect.cpp index 656a61991..e461f332e 100644 --- a/Source/Engine/Particles/ParticleEffect.cpp +++ b/Source/Engine/Particles/ParticleEffect.cpp @@ -283,11 +283,8 @@ void ParticleEffect::UpdateSimulation(bool singleFrame) void ParticleEffect::Play() { - // Reset simulation when play is called and particle system is time is complete - ex. if IsLooping is false and simulation is complete - if (!IsLooping && Instance.Time >= (float)ParticleSystem->DurationFrames / ParticleSystem->FramesPerSecond) - ResetSimulation(); - _isPlaying = true; + _isStopped = false; } void ParticleEffect::Pause() @@ -297,6 +294,7 @@ void ParticleEffect::Pause() void ParticleEffect::Stop() { + _isStopped = true; _isPlaying = false; ResetSimulation(); } @@ -452,7 +450,7 @@ void ParticleEffect::Update() void ParticleEffect::UpdateExecuteInEditor() { // Auto-play in Editor - if (!Editor::IsPlayMode) + if (!Editor::IsPlayMode && !_isStopped) { _isPlaying = true; Update(); diff --git a/Source/Engine/Particles/ParticleEffect.h b/Source/Engine/Particles/ParticleEffect.h index 07d82d703..d3f0cc9d0 100644 --- a/Source/Engine/Particles/ParticleEffect.h +++ b/Source/Engine/Particles/ParticleEffect.h @@ -185,6 +185,7 @@ private: Array _parameters; // Cached for scripting API Array _parametersOverrides; // Cached parameter modifications to be applied to the parameters bool _isPlaying = false; + bool _isStopped = false; public: /// diff --git a/Source/Engine/Particles/Particles.cpp b/Source/Engine/Particles/Particles.cpp index 15d484f8a..52845a0f8 100644 --- a/Source/Engine/Particles/Particles.cpp +++ b/Source/Engine/Particles/Particles.cpp @@ -1318,8 +1318,8 @@ void ParticlesSystem::Job(int32 index) emitterInstance.Buffer = nullptr; } } - // Set is playing to false because it is not playing anymore. - effect->Pause(); + // Stop playing effect. + effect->Stop(); return; } } From 9fa0b174f52d5129baaa674822f74450d18a0f40 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 22 Oct 2023 15:32:56 +0200 Subject: [PATCH 39/78] Fix deprecation warnings on Apple --- Source/Engine/Platform/Unix/UnixNetwork.cpp | 4 ++++ Source/ThirdParty/stb/stb_image_write.h | 2 ++ 2 files changed, 6 insertions(+) diff --git a/Source/Engine/Platform/Unix/UnixNetwork.cpp b/Source/Engine/Platform/Unix/UnixNetwork.cpp index 5ffdb2c01..ae2d8d9f2 100644 --- a/Source/Engine/Platform/Unix/UnixNetwork.cpp +++ b/Source/Engine/Platform/Unix/UnixNetwork.cpp @@ -96,7 +96,11 @@ static bool CreateEndPointFromAddr(sockaddr* addr, NetworkEndPoint& endPoint) return true; } char strPort[6]; +#if __APPLE__ + snprintf(strPort, sizeof(strPort), "%d", port); +#else sprintf(strPort, "%d", port); +#endif endPoint.IPVersion = addr->sa_family == AF_INET6 ? NetworkIPVersion::IPv6 : NetworkIPVersion::IPv4; memcpy(endPoint.Data, addr, size); return false; diff --git a/Source/ThirdParty/stb/stb_image_write.h b/Source/ThirdParty/stb/stb_image_write.h index 95943eb60..8cae247eb 100644 --- a/Source/ThirdParty/stb/stb_image_write.h +++ b/Source/ThirdParty/stb/stb_image_write.h @@ -758,6 +758,8 @@ static int stbi_write_hdr_core(stbi__write_context *s, int x, int y, int comp, f #ifdef __STDC_WANT_SECURE_LIB__ len = sprintf_s(buffer, sizeof(buffer), "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x); +#elif __APPLE__ + len = snprintf(buffer, sizeof(buffer), "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x); #else len = sprintf(buffer, "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x); #endif From 1280e61af0184585df5c640cc7d4a9a3cee03d36 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 22 Oct 2023 15:33:21 +0200 Subject: [PATCH 40/78] Add IsDebuggerPresent for macOS and iOS platforms --- .../Engine/Platform/Apple/ApplePlatform.cpp | 29 ++++++++++++++++++- Source/Engine/Platform/Apple/ApplePlatform.h | 3 ++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Platform/Apple/ApplePlatform.cpp b/Source/Engine/Platform/Apple/ApplePlatform.cpp index bf79c95c2..523f3b561 100644 --- a/Source/Engine/Platform/Apple/ApplePlatform.cpp +++ b/Source/Engine/Platform/Apple/ApplePlatform.cpp @@ -245,13 +245,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()) diff --git a/Source/Engine/Platform/Apple/ApplePlatform.h b/Source/Engine/Platform/Apple/ApplePlatform.h index f1148d0b6..94b6534d6 100644 --- a/Source/Engine/Platform/Apple/ApplePlatform.h +++ b/Source/Engine/Platform/Apple/ApplePlatform.h @@ -79,7 +79,10 @@ public: static uint64 GetClockFrequency(); static void GetSystemTime(int32& year, int32& month, int32& dayOfWeek, int32& day, int32& hour, int32& minute, int32& second, int32& millisecond); static void GetUTCTime(int32& year, int32& month, int32& dayOfWeek, int32& day, int32& hour, int32& minute, int32& second, int32& millisecond); +#if !BUILD_RELEASE static void Log(const StringView& msg); + static bool IsDebuggerPresent(); +#endif static bool Init(); static void Tick(); static void BeforeExit(); From c88e184df363ab006428b2f40789783ff4ede30b Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 22 Oct 2023 15:56:25 +0200 Subject: [PATCH 41/78] Fix crash when window gets removed during windows update loop --- Source/Editor/Managed/ManagedEditor.Internal.cpp | 4 +++- Source/Engine/Platform/Base/WindowsManager.cpp | 9 ++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Source/Editor/Managed/ManagedEditor.Internal.cpp b/Source/Editor/Managed/ManagedEditor.Internal.cpp index e01fd04bb..969608676 100644 --- a/Source/Editor/Managed/ManagedEditor.Internal.cpp +++ b/Source/Editor/Managed/ManagedEditor.Internal.cpp @@ -513,7 +513,9 @@ DEFINE_INTERNAL_CALL(void) EditorInternal_RunVisualScriptBreakpointLoopTick(floa WindowsManager::WindowsLocker.Unlock(); } WindowsManager::WindowsLocker.Lock(); - for (auto& win : WindowsManager::Windows) + Array> windows; + windows.Add(WindowsManager::Windows); + for (Window* win : windows) { if (win->IsVisible()) win->OnUpdate(deltaTime); diff --git a/Source/Engine/Platform/Base/WindowsManager.cpp b/Source/Engine/Platform/Base/WindowsManager.cpp index 0d58008d8..34381b3d2 100644 --- a/Source/Engine/Platform/Base/WindowsManager.cpp +++ b/Source/Engine/Platform/Base/WindowsManager.cpp @@ -59,9 +59,11 @@ void WindowsManagerService::Update() // Update windows const float deltaTime = Time::Update.UnscaledDeltaTime.GetTotalSeconds(); WindowsManager::WindowsLocker.Lock(); - for (Window* win : WindowsManager::Windows) + Array> windows; + windows.Add(WindowsManager::Windows); + for (Window* win : windows) { - if (win && win->IsVisible()) + if (win->IsVisible()) win->OnUpdate(deltaTime); } WindowsManager::WindowsLocker.Unlock(); @@ -71,7 +73,8 @@ void WindowsManagerService::Dispose() { // Close remaining windows WindowsManager::WindowsLocker.Lock(); - auto windows = WindowsManager::Windows; + Array> windows; + windows.Add(WindowsManager::Windows); for (Window* win : windows) { win->Close(ClosingReason::EngineExit); From ccf6c28b02b0d6d140000391a8d440af8a977406 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 22 Oct 2023 19:55:20 +0200 Subject: [PATCH 42/78] Add interval to Apple autoreleasepool --- .../Engine/Platform/Apple/ApplePlatform.cpp | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/Source/Engine/Platform/Apple/ApplePlatform.cpp b/Source/Engine/Platform/Apple/ApplePlatform.cpp index 523f3b561..d4348b107 100644 --- a/Source/Engine/Platform/Apple/ApplePlatform.cpp +++ b/Source/Engine/Platform/Apple/ApplePlatform.cpp @@ -48,6 +48,7 @@ CPUInfo Cpu; String UserLocale; double SecondsPerCycle; NSAutoreleasePool* AutoreleasePool = nullptr; +int32 AutoreleasePoolInterval = 0; float ApplePlatform::ScreenScale = 1.0f; @@ -359,9 +360,13 @@ bool ApplePlatform::Init() void ApplePlatform::Tick() { - // TODO: do it once every X fames - [AutoreleasePool drain]; - AutoreleasePool = [[NSAutoreleasePool alloc] init]; + AutoreleasePoolInterval++; + if (AutoreleasePoolInterval >= 60) + { + AutoreleasePoolInterval = 0; + [AutoreleasePool drain]; + AutoreleasePool = [[NSAutoreleasePool alloc] init]; + } } void ApplePlatform::BeforeExit() @@ -370,6 +375,11 @@ void ApplePlatform::BeforeExit() void ApplePlatform::Exit() { + if (AutoreleasePool) + { + [AutoreleasePool drain]; + AutoreleasePool = nullptr; + } } void ApplePlatform::SetHighDpiAwarenessEnabled(bool enable) @@ -396,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) From 6ff3e0f48876757532e645132b6e6c7e5050fb2a Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 22 Oct 2023 20:06:54 +0200 Subject: [PATCH 43/78] Various improvements to macOS platform --- Source/Editor/GUI/Docking/DockHintWindow.cs | 28 +++++++-------- .../Engine/Platform/Linux/LinuxPlatform.cpp | 4 +-- Source/Engine/Platform/Mac/MacWindow.cpp | 35 +++++++++++++------ Source/Engine/Platform/Mac/MacWindow.h | 6 ++-- Source/Engine/Platform/iOS/iOSPlatform.cpp | 2 +- 5 files changed, 42 insertions(+), 33 deletions(-) diff --git a/Source/Editor/GUI/Docking/DockHintWindow.cs b/Source/Editor/GUI/Docking/DockHintWindow.cs index 52c5dcd3c..7d459e163 100644 --- a/Source/Editor/GUI/Docking/DockHintWindow.cs +++ b/Source/Editor/GUI/Docking/DockHintWindow.cs @@ -83,6 +83,7 @@ namespace FlaxEditor.GUI.Docking // Enable hit window presentation Proxy.Window.RenderingEnabled = true; Proxy.Window.Show(); + Proxy.Window.Focus(); } /// @@ -270,15 +271,16 @@ namespace FlaxEditor.GUI.Docking // Cache dock rectangles var size = _rectDock.Size; var offset = _rectDock.Location; - float BorderMargin = 4.0f; - float ProxyHintWindowsSize2 = Proxy.HintWindowsSize * 0.5f; - float centerX = size.X * 0.5f; - float centerY = size.Y * 0.5f; - _rUpper = new Rectangle(centerX - ProxyHintWindowsSize2, BorderMargin, Proxy.HintWindowsSize, Proxy.HintWindowsSize) + offset; - _rBottom = new Rectangle(centerX - ProxyHintWindowsSize2, size.Y - Proxy.HintWindowsSize - BorderMargin, Proxy.HintWindowsSize, Proxy.HintWindowsSize) + offset; - _rLeft = new Rectangle(BorderMargin, centerY - ProxyHintWindowsSize2, Proxy.HintWindowsSize, Proxy.HintWindowsSize) + offset; - _rRight = new Rectangle(size.X - Proxy.HintWindowsSize - BorderMargin, centerY - ProxyHintWindowsSize2, Proxy.HintWindowsSize, Proxy.HintWindowsSize) + offset; - _rCenter = new Rectangle(centerX - ProxyHintWindowsSize2, centerY - ProxyHintWindowsSize2, Proxy.HintWindowsSize, Proxy.HintWindowsSize) + offset; + var borderMargin = 4.0f; + var hintWindowsSize = Proxy.HintWindowsSize * Platform.DpiScale; + var hintWindowsSize2 = hintWindowsSize * 0.5f; + var centerX = size.X * 0.5f; + var centerY = size.Y * 0.5f; + _rUpper = new Rectangle(centerX - hintWindowsSize2, borderMargin, hintWindowsSize, hintWindowsSize) + offset; + _rBottom = new Rectangle(centerX - hintWindowsSize2, size.Y - hintWindowsSize - borderMargin, hintWindowsSize, hintWindowsSize) + offset; + _rLeft = new Rectangle(borderMargin, centerY - hintWindowsSize2, hintWindowsSize, hintWindowsSize) + offset; + _rRight = new Rectangle(size.X - hintWindowsSize - borderMargin, centerY - hintWindowsSize2, hintWindowsSize, hintWindowsSize) + offset; + _rCenter = new Rectangle(centerX - hintWindowsSize2, centerY - hintWindowsSize2, hintWindowsSize, hintWindowsSize) + offset; // Hit test DockState toSet = DockState.Float; @@ -428,7 +430,6 @@ namespace FlaxEditor.GUI.Docking { if (Window == null) { - // Create proxy window var settings = CreateWindowSettings.Default; settings.Title = "DockHint.Window"; settings.Size = initSize; @@ -440,12 +441,10 @@ namespace FlaxEditor.GUI.Docking settings.IsRegularWindow = false; settings.SupportsTransparency = true; settings.ShowInTaskbar = false; - settings.ShowAfterFirstPaint = true; + settings.ShowAfterFirstPaint = false; settings.IsTopmost = true; Window = Platform.CreateWindow(ref settings); - - // Set opacity and background color Window.Opacity = 0.6f; Window.GUI.BackgroundColor = Style.Current.DragWindow; } @@ -465,7 +464,7 @@ namespace FlaxEditor.GUI.Docking var settings = CreateWindowSettings.Default; settings.Title = name; - settings.Size = new Float2(HintWindowsSize); + settings.Size = new Float2(HintWindowsSize * Platform.DpiScale); settings.AllowInput = false; settings.AllowMaximize = false; settings.AllowMinimize = false; @@ -479,7 +478,6 @@ namespace FlaxEditor.GUI.Docking settings.ShowAfterFirstPaint = false; win = Platform.CreateWindow(ref settings); - win.Opacity = 0.6f; win.GUI.BackgroundColor = Style.Current.DragWindow; } diff --git a/Source/Engine/Platform/Linux/LinuxPlatform.cpp b/Source/Engine/Platform/Linux/LinuxPlatform.cpp index 4787b4549..36affd8c8 100644 --- a/Source/Engine/Platform/Linux/LinuxPlatform.cpp +++ b/Source/Engine/Platform/Linux/LinuxPlatform.cpp @@ -2622,9 +2622,7 @@ bool LinuxPlatform::GetHasFocus() if (window->IsFocused()) return true; } - - // Default to true if has no windows open - return WindowsManager::Windows.IsEmpty(); + return false; } bool LinuxPlatform::CanOpenUrl(const StringView& url) diff --git a/Source/Engine/Platform/Mac/MacWindow.cpp b/Source/Engine/Platform/Mac/MacWindow.cpp index 08a4e0d2f..ea38a635c 100644 --- a/Source/Engine/Platform/Mac/MacWindow.cpp +++ b/Source/Engine/Platform/Mac/MacWindow.cpp @@ -710,6 +710,8 @@ MacWindow::MacWindow(const CreateWindowSettings& settings) { styleMask |= NSWindowStyleMaskBorderless; } + if (settings.Fullscreen) + styleMask |= NSWindowStyleMaskFullScreen; if (settings.HasBorder) { styleMask |= NSWindowStyleMaskTitled; @@ -736,7 +738,8 @@ MacWindow::MacWindow(const CreateWindowSettings& settings) [window setMaxSize:NSMakeSize(settings.MaximumSize.X, settings.MaximumSize.Y)]; [window setOpaque:!settings.SupportsTransparency]; [window setContentView:view]; - [window setAcceptsMouseMovedEvents:YES]; + if (settings.AllowInput) + [window setAcceptsMouseMovedEvents:YES]; [window setDelegate:window]; _window = window; _view = view; @@ -751,8 +754,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 } @@ -816,7 +817,10 @@ void MacWindow::Show() // Show NSWindow* window = (NSWindow*)_window; - [window makeKeyAndOrderFront:window]; + if (_settings.AllowInput) + [window makeKeyAndOrderFront:window]; + else + [window orderFront:window]; if (_settings.ActivateWhenFirstShown) [NSApp activateIgnoringOtherApps:YES]; _focused = true; @@ -870,14 +874,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) @@ -1039,6 +1038,22 @@ DragDropEffect MacWindow::DoDragDrop(const StringView& data) return result; } +void MacWindow::StartTrackingMouse(bool useMouseScreenOffset) +{ + if (_isTrackingMouse || !_window) + return; + _isTrackingMouse = true; + _trackingMouseOffset = Float2::Zero; + _isUsingMouseOffset = useMouseScreenOffset; +} + +void MacWindow::EndTrackingMouse() +{ + if (!_isTrackingMouse || !_window) + return; + _isTrackingMouse = false; +} + void MacWindow::SetCursor(CursorType type) { CursorType prev = _cursor; diff --git a/Source/Engine/Platform/Mac/MacWindow.h b/Source/Engine/Platform/Mac/MacWindow.h index 7c3f091b4..eb1389f29 100644 --- a/Source/Engine/Platform/Mac/MacWindow.h +++ b/Source/Engine/Platform/Mac/MacWindow.h @@ -13,14 +13,12 @@ class FLAXENGINE_API MacWindow : public WindowBase { private: - void* _window = nullptr; void* _view = nullptr; bool _isMouseOver = false; String _dragText; public: - MacWindow(const CreateWindowSettings& settings); ~MacWindow(); @@ -32,7 +30,6 @@ public: } public: - // [WindowBase] void* GetNativePtr() const override; void Show() override; @@ -40,7 +37,6 @@ public: 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; @@ -56,6 +52,8 @@ public: void Focus() override; void SetTitle(const StringView& title) override; DragDropEffect DoDragDrop(const StringView& data) override; + void StartTrackingMouse(bool useMouseScreenOffset) override; + void EndTrackingMouse() override; void SetCursor(CursorType type) override; }; diff --git a/Source/Engine/Platform/iOS/iOSPlatform.cpp b/Source/Engine/Platform/iOS/iOSPlatform.cpp index 7ca27c481..26c3b8f28 100644 --- a/Source/Engine/Platform/iOS/iOSPlatform.cpp +++ b/Source/Engine/Platform/iOS/iOSPlatform.cpp @@ -410,7 +410,7 @@ bool iOSWindow::IsClosed() const bool iOSWindow::IsForegroundWindow() const { - return Platform::GetHasFocus() && IsFocused(); + return IsFocused() && Platform::GetHasFocus(); } void iOSWindow::BringToFront(bool force) From f28947f59b61f6322a180ea027d26b3bd53b0375 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 23 Oct 2023 14:50:10 +0200 Subject: [PATCH 44/78] Fix MacWindow::SetClientBounds to include screen scale --- Source/Engine/Platform/Mac/MacWindow.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Source/Engine/Platform/Mac/MacWindow.cpp b/Source/Engine/Platform/Mac/MacWindow.cpp index ea38a635c..0274ecb9a 100644 --- a/Source/Engine/Platform/Mac/MacWindow.cpp +++ b/Source/Engine/Platform/Mac/MacWindow.cpp @@ -895,14 +895,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; From 9c4382dffb31de90c571b75a241c120171bb158c Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 23 Oct 2023 15:59:09 +0200 Subject: [PATCH 45/78] Fix using dock window panels on macOS in Editor --- Source/Editor/GUI/Docking/DockHintWindow.cs | 10 ++--- Source/Engine/Platform/Mac/MacWindow.cpp | 44 ++++++++++++++++++--- Source/Engine/Platform/Mac/MacWindow.h | 3 ++ 3 files changed, 46 insertions(+), 11 deletions(-) diff --git a/Source/Editor/GUI/Docking/DockHintWindow.cs b/Source/Editor/GUI/Docking/DockHintWindow.cs index 7d459e163..6e2353441 100644 --- a/Source/Editor/GUI/Docking/DockHintWindow.cs +++ b/Source/Editor/GUI/Docking/DockHintWindow.cs @@ -44,11 +44,11 @@ namespace FlaxEditor.GUI.Docking var mousePos = window.MousePosition; var previousSize = window.Size; window.Restore(); - window.Position = FlaxEngine.Input.MouseScreenPosition - mousePos * window.Size / previousSize; + window.Position = Platform.MousePosition - mousePos * window.Size / previousSize; } // Calculate dragging offset and move window to the destination position - var mouseScreenPosition = FlaxEngine.Input.MouseScreenPosition; + var mouseScreenPosition = Platform.MousePosition; // If the _toMove window was not focused when initializing this window, the result vector only contains zeros // and to prevent a failure, we need to perform an update for the drag offset at later time which will be done in the OnMouseMove event handler. @@ -114,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; @@ -194,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 @@ -245,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; diff --git a/Source/Engine/Platform/Mac/MacWindow.cpp b/Source/Engine/Platform/Mac/MacWindow.cpp index 0274ecb9a..38e8c95df 100644 --- a/Source/Engine/Platform/Mac/MacWindow.cpp +++ b/Source/Engine/Platform/Mac/MacWindow.cpp @@ -498,7 +498,9 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r) { 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); } @@ -521,11 +523,12 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r) { 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 @@ -537,8 +540,21 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r) { 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 @@ -788,7 +804,6 @@ void MacWindow::SetIsMouseOver(bool value) // Refresh cursor typet SetCursor(CursorType::Default); SetCursor(cursor); - } else { @@ -803,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) @@ -838,7 +869,7 @@ void MacWindow::Hide() // Hide NSWindow* window = (NSWindow*)_window; - [window orderOut:nil]; + [window orderOut:window]; // Base WindowBase::Hide(); @@ -1044,6 +1075,7 @@ void MacWindow::StartTrackingMouse(bool useMouseScreenOffset) _isTrackingMouse = true; _trackingMouseOffset = Float2::Zero; _isUsingMouseOffset = useMouseScreenOffset; + _mouseTrackPos = Float2::Minimum; } void MacWindow::EndTrackingMouse() diff --git a/Source/Engine/Platform/Mac/MacWindow.h b/Source/Engine/Platform/Mac/MacWindow.h index eb1389f29..ccd43b354 100644 --- a/Source/Engine/Platform/Mac/MacWindow.h +++ b/Source/Engine/Platform/Mac/MacWindow.h @@ -6,6 +6,7 @@ #include "Engine/Platform/Base/WindowBase.h" #include "Engine/Platform/Platform.h" +#include "Engine/Core/Math/Vector2.h" /// /// Implementation of the window class for Mac platform. @@ -16,6 +17,7 @@ private: void* _window = nullptr; void* _view = nullptr; bool _isMouseOver = false; + Float2 _mouseTrackPos = Float2::Minimum; String _dragText; public: @@ -32,6 +34,7 @@ public: public: // [WindowBase] void* GetNativePtr() const override; + void OnUpdate(float dt) override; void Show() override; void Hide() override; void Minimize() override; From aed81b58bae0d0341d01b3bab7b259d49973cd6e Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 20 Oct 2023 14:20:07 +0200 Subject: [PATCH 46/78] Update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fac631a6a..d6688bd03 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Flax Engine is a high quality modern 3D game engine written in C++ and C#. -From stunning graphics to powerful scripts - Flax can give everything for your games. Designed for fast workflow with many ready to use features waiting for you right now. To learn more see the website ([www.flaxengine.com](https://flaxengine.com)). +From stunning graphics to powerful scripts, it's designed for fast workflow with many ready-to-use features waiting for you right now. To learn more see the website ([www.flaxengine.com](https://flaxengine.com)). This repository contains full source code of the Flax Engine (excluding NDA-protected platforms support). Anyone is welcome to contribute or use the modified source in Flax-based games. From 9d1ba6cacfc7f2e812c544f3b67a51c17cdb6f9e Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 23 Oct 2023 22:10:21 +0200 Subject: [PATCH 47/78] Fix compilation errors when building with old `Delegate` impl --- Source/Engine/Core/ObjectsRemovalService.cpp | 1 + Source/Engine/Level/SceneObjectsFactory.cpp | 1 + Source/Engine/Navigation/NavCrowd.cpp | 1 + 3 files changed, 3 insertions(+) diff --git a/Source/Engine/Core/ObjectsRemovalService.cpp b/Source/Engine/Core/ObjectsRemovalService.cpp index a020f7844..a185ce8b3 100644 --- a/Source/Engine/Core/ObjectsRemovalService.cpp +++ b/Source/Engine/Core/ObjectsRemovalService.cpp @@ -5,6 +5,7 @@ #include "Collections/Dictionary.h" #include "Engine/Engine/Time.h" #include "Engine/Engine/EngineService.h" +#include "Engine/Platform/CriticalSection.h" #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Scripting/ScriptingObject.h" diff --git a/Source/Engine/Level/SceneObjectsFactory.cpp b/Source/Engine/Level/SceneObjectsFactory.cpp index 5632d8bc7..3bf0f68a1 100644 --- a/Source/Engine/Level/SceneObjectsFactory.cpp +++ b/Source/Engine/Level/SceneObjectsFactory.cpp @@ -16,6 +16,7 @@ #include "Engine/Threading/ThreadLocal.h" #if !BUILD_RELEASE || USE_EDITOR #include "Engine/Level/Level.h" +#include "Engine/Threading/Threading.h" #endif SceneObjectsFactory::Context::Context(ISerializeModifier* modifier) diff --git a/Source/Engine/Navigation/NavCrowd.cpp b/Source/Engine/Navigation/NavCrowd.cpp index b05f0f7d3..fe1c2f728 100644 --- a/Source/Engine/Navigation/NavCrowd.cpp +++ b/Source/Engine/Navigation/NavCrowd.cpp @@ -7,6 +7,7 @@ #include "Engine/Level/Level.h" #include "Engine/Level/Scene/Scene.h" #include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Threading/Threading.h" #include NavCrowd::NavCrowd(const SpawnParams& params) From ba374a27db88a3fe342efb404e513313aab478cb Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 23 Oct 2023 22:26:55 +0200 Subject: [PATCH 48/78] Reduce code bloat --- Source/Engine/Core/Delegate.h | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/Source/Engine/Core/Delegate.h b/Source/Engine/Core/Delegate.h index 4e081faef..821d7916e 100644 --- a/Source/Engine/Core/Delegate.h +++ b/Source/Engine/Core/Delegate.h @@ -583,18 +583,9 @@ public: template void Unbind() { -#if DELEGATE_USE_ATOMIC FunctionType f; f.template Bind(); Unbind(f); -#else - if (_functions == nullptr) - return; - FunctionType f; - f.template Bind(); - ScopeLock lock(*_locker); - _functions->Remove(f); -#endif } /// @@ -604,18 +595,9 @@ public: template void Unbind(T* callee) { -#if DELEGATE_USE_ATOMIC FunctionType f; f.template Bind(callee); Unbind(f); -#else - if (_functions == nullptr) - return; - FunctionType f; - f.template Bind(callee); - ScopeLock lock(*_locker); - _functions->Remove(f); -#endif } /// @@ -624,16 +606,8 @@ public: /// The method. void Unbind(Signature method) { -#if DELEGATE_USE_ATOMIC FunctionType f(method); Unbind(f); -#else - if (_functions == nullptr) - return; - FunctionType f(method); - ScopeLock lock(*_locker); - _functions->Remove(f); -#endif } /// From 806590d1c3350136ae6d0043fa22e06a3faf7002 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 24 Oct 2023 14:08:34 +0200 Subject: [PATCH 49/78] Fix deadlock in scene loading #1761 #1773 --- Source/Engine/Level/Level.cpp | 34 ++----------------- Source/Engine/Level/Level.h | 4 +-- Source/Engine/Level/SceneObjectsFactory.cpp | 6 ++-- .../Platform/Win32/Win32CriticalSection.h | 10 +----- 4 files changed, 9 insertions(+), 45 deletions(-) diff --git a/Source/Engine/Level/Level.cpp b/Source/Engine/Level/Level.cpp index d7659ca1c..cfdf11a6f 100644 --- a/Source/Engine/Level/Level.cpp +++ b/Source/Engine/Level/Level.cpp @@ -416,7 +416,7 @@ public: } // Load scene - if (Level::loadScene(SceneAsset.Get())) + if (Level::loadScene(SceneAsset)) { LOG(Error, "Failed to deserialize scene {0}", SceneId); CallSceneEvent(SceneEventType::OnSceneLoadError, nullptr, SceneId); @@ -816,40 +816,10 @@ bool LevelImpl::unloadScenes() return false; } -bool Level::loadScene(const Guid& sceneId) -{ - const auto sceneAsset = Content::LoadAsync(sceneId); - return loadScene(sceneAsset); -} - -bool Level::loadScene(const String& scenePath) -{ - LOG(Info, "Loading scene from file. Path: \'{0}\'", scenePath); - - // Check for missing file - if (!FileSystem::FileExists(scenePath)) - { - LOG(Error, "Missing scene file."); - return true; - } - - // Load file - BytesContainer sceneData; - if (File::ReadAllBytes(scenePath, sceneData)) - { - LOG(Error, "Cannot load data from file."); - return true; - } - - return loadScene(sceneData); -} - bool Level::loadScene(JsonAsset* sceneAsset) { // Keep reference to the asset (prevent unloading during action) AssetReference ref = sceneAsset; - - // Wait for loaded if (sceneAsset == nullptr || sceneAsset->WaitForLoaded()) { LOG(Error, "Cannot load scene asset."); @@ -879,6 +849,7 @@ bool Level::loadScene(const BytesContainer& sceneData, Scene** outScene) return true; } + ScopeLock lock(ScenesLock); return loadScene(document, outScene); } @@ -1332,6 +1303,7 @@ bool Level::LoadScene(const Guid& id) } // Load scene + ScopeLock lock(ScenesLock); if (loadScene(sceneAsset)) { LOG(Error, "Failed to deserialize scene {0}", id); diff --git a/Source/Engine/Level/Level.h b/Source/Engine/Level/Level.h index 1f0acda2d..e09f8e376 100644 --- a/Source/Engine/Level/Level.h +++ b/Source/Engine/Level/Level.h @@ -541,8 +541,8 @@ private: }; static void callActorEvent(ActorEventType eventType, Actor* a, Actor* b); - static bool loadScene(const Guid& sceneId); - static bool loadScene(const String& scenePath); + + // All loadScene assume that ScenesLock has been taken by the calling thread static bool loadScene(JsonAsset* sceneAsset); static bool loadScene(const BytesContainer& sceneData, Scene** outScene = nullptr); static bool loadScene(rapidjson_flax::Document& document, Scene** outScene = nullptr); diff --git a/Source/Engine/Level/SceneObjectsFactory.cpp b/Source/Engine/Level/SceneObjectsFactory.cpp index 3bf0f68a1..2936da3da 100644 --- a/Source/Engine/Level/SceneObjectsFactory.cpp +++ b/Source/Engine/Level/SceneObjectsFactory.cpp @@ -280,9 +280,9 @@ void SceneObjectsFactory::HandleObjectDeserializationError(const ISerializable:: #if USE_EDITOR // Add dummy script auto* dummyScript = parent->AddScript(); - const auto parentIdMember = value.FindMember("TypeName"); - if (parentIdMember != value.MemberEnd() && parentIdMember->value.IsString()) - dummyScript->MissingTypeName = parentIdMember->value.GetString(); + const auto typeNameMember = value.FindMember("TypeName"); + if (typeNameMember != value.MemberEnd() && typeNameMember->value.IsString()) + dummyScript->MissingTypeName = typeNameMember->value.GetString(); dummyScript->Data = MoveTemp(bufferStr); #endif LOG(Warning, "Parent actor of the missing object: {0}", parent->GetName()); diff --git a/Source/Engine/Platform/Win32/Win32CriticalSection.h b/Source/Engine/Platform/Win32/Win32CriticalSection.h index 2c103ef85..95d85efac 100644 --- a/Source/Engine/Platform/Win32/Win32CriticalSection.h +++ b/Source/Engine/Platform/Win32/Win32CriticalSection.h @@ -16,16 +16,13 @@ class FLAXENGINE_API Win32CriticalSection friend Win32ConditionVariable; private: - mutable Windows::CRITICAL_SECTION _criticalSection; private: - Win32CriticalSection(const Win32CriticalSection&); Win32CriticalSection& operator=(const Win32CriticalSection&); public: - /// /// Initializes a new instance of the class. /// @@ -43,17 +40,12 @@ public: } public: - /// /// Locks the critical section. /// void Lock() const { - // Spin first before entering critical section, causing ring-0 transition and context switch - if (Windows::TryEnterCriticalSection(&_criticalSection) == 0) - { - Windows::EnterCriticalSection(&_criticalSection); - } + Windows::EnterCriticalSection(&_criticalSection); } /// From 6f773bd558a760eb0d347042181b29b8cf16dbb1 Mon Sep 17 00:00:00 2001 From: MineBill Date: Tue, 24 Oct 2023 18:03:07 +0300 Subject: [PATCH 50/78] Make the message box a TASKMODEL to prevent interation with the editor. --- Source/Engine/Platform/Windows/WindowsPlatform.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/Engine/Platform/Windows/WindowsPlatform.cpp b/Source/Engine/Platform/Windows/WindowsPlatform.cpp index c5bc44ab9..474a92072 100644 --- a/Source/Engine/Platform/Windows/WindowsPlatform.cpp +++ b/Source/Engine/Platform/Windows/WindowsPlatform.cpp @@ -451,6 +451,7 @@ DialogResult MessageBox::Show(Window* parent, const StringView& text, const Stri default: break; } + flags |= MB_TASKMODAL; // Show dialog int result = MessageBoxW(parent ? static_cast(parent->GetNativePtr()) : nullptr, String(text).GetText(), String(caption).GetText(), flags); From daf31cfa4dc26f6c87dde163a43efab100a2ded9 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 25 Oct 2023 14:57:44 +0200 Subject: [PATCH 51/78] Format code #1611 --- Source/Editor/Viewport/EditorViewport.cs | 27 +++++++++++++++--------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/Source/Editor/Viewport/EditorViewport.cs b/Source/Editor/Viewport/EditorViewport.cs index 8fd28ebfd..c49392d01 100644 --- a/Source/Editor/Viewport/EditorViewport.cs +++ b/Source/Editor/Viewport/EditorViewport.cs @@ -212,8 +212,10 @@ namespace FlaxEditor.Viewport /// /// Format of the text for the camera move speed. /// - private string MovementSpeedTextFormat { - get { + private string MovementSpeedTextFormat + { + get + { if (Mathf.Abs(_movementSpeed - _maxMovementSpeed) < Mathf.Epsilon || Mathf.Abs(_movementSpeed - _minMovementSpeed) < Mathf.Epsilon) return "{0:0.##}"; @@ -480,7 +482,7 @@ namespace FlaxEditor.Viewport get => _panningSpeed; set => _panningSpeed = value; } - + /// /// The input actions collection to processed during user input. /// @@ -544,6 +546,7 @@ namespace FlaxEditor.Viewport if (useWidgets) { #region Camera settings widget + var largestText = "Relative Panning"; var textSize = Style.Current.FontMedium.MeasureText(largestText); var xLocationForExtras = textSize.X + 5; @@ -609,7 +612,7 @@ namespace FlaxEditor.Viewport maxCamSpeedValue.ValueChanged += () => { OnMaxMovementSpeedChanged(maxCamSpeedValue); - + minCamSpeedValue.MaxValue = maxCamSpeedValue.Value; if (Math.Abs(camSpeedValue.MaxValue - maxCamSpeedValue.Value) > Mathf.Epsilon) @@ -792,9 +795,11 @@ namespace FlaxEditor.Viewport maxCamSpeedValue.Value = _maxMovementSpeed; }; } + #endregion Camera settings widget #region View mode widget + largestText = "Brightness"; textSize = Style.Current.FontMedium.MeasureText(largestText); xLocationForExtras = textSize.X + 5; @@ -964,6 +969,7 @@ namespace FlaxEditor.Viewport resolutionValue.ValueChanged += () => ResolutionScale = resolutionValue.Value; ViewWidgetButtonMenu.VisibleChanged += control => resolutionValue.Value = ResolutionScale; } + #endregion View mode widget } @@ -1036,7 +1042,7 @@ namespace FlaxEditor.Viewport OnCameraMovementProgressChanged(); _editor.ProjectCache.SetCustomData("CameraMaxMovementSpeedValue", _maxMovementSpeed.ToString()); } - + private void OnCameraEasingToggled(Control control) { _useCameraEasing = !_useCameraEasing; @@ -1145,11 +1151,12 @@ namespace FlaxEditor.Viewport private void OnCameraMovementProgressChanged() { // prevent NaN - if (Math.Abs(_minMovementSpeed - _maxMovementSpeed) < Mathf.Epsilon) { + if (Math.Abs(_minMovementSpeed - _maxMovementSpeed) < Mathf.Epsilon) + { _speedStep = 0; return; } - + if (Math.Abs(_movementSpeed - _maxMovementSpeed) < Mathf.Epsilon) { _speedStep = _maxSpeedSteps; @@ -1180,9 +1187,9 @@ namespace FlaxEditor.Viewport // calculate new linear/eased progress var progress = _useCameraEasing - ? Mathf.Pow((float)_speedStep / _maxSpeedSteps, _cameraEasingDegree) - : (float)_speedStep / _maxSpeedSteps; - + ? 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()); From 966cd973c660e76feeac8547a8314960f389e967 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 25 Oct 2023 18:35:59 +0200 Subject: [PATCH 52/78] Add `GetHash` to various math/core types #1802 --- Source/Engine/Core/Math/Vector2.h | 6 ++++++ Source/Engine/Core/Math/Vector3.h | 6 ++++++ Source/Engine/Core/Math/Vector4.h | 6 ++++++ 3 files changed, 18 insertions(+) diff --git a/Source/Engine/Core/Math/Vector2.h b/Source/Engine/Core/Math/Vector2.h index 83deb009a..cea03ec10 100644 --- a/Source/Engine/Core/Math/Vector2.h +++ b/Source/Engine/Core/Math/Vector2.h @@ -646,6 +646,12 @@ inline Vector2Base operator/(typename TOtherFloat::Type a, const Vector2Ba return Vector2Base(a) / b; } +template +inline uint32 GetHash(const Vector2Base& key) +{ + return (*(uint32*)&key.X * 397) ^ *(uint32*)&key.Y; +} + namespace Math { template diff --git a/Source/Engine/Core/Math/Vector3.h b/Source/Engine/Core/Math/Vector3.h index c0ebdf01d..01b55e9bd 100644 --- a/Source/Engine/Core/Math/Vector3.h +++ b/Source/Engine/Core/Math/Vector3.h @@ -977,6 +977,12 @@ inline Vector3Base operator/(typename TOtherFloat::Type a, const Vector3Ba return Vector3Base(a) / b; } +template +inline uint32 GetHash(const Vector3Base& key) +{ + return (((*(uint32*)&key.X * 397) ^ *(uint32*)&key.Y) * 397) ^ *(uint32*)&key.Z; +} + namespace Math { template diff --git a/Source/Engine/Core/Math/Vector4.h b/Source/Engine/Core/Math/Vector4.h index 5c7b24c4a..1cc6d4db8 100644 --- a/Source/Engine/Core/Math/Vector4.h +++ b/Source/Engine/Core/Math/Vector4.h @@ -552,6 +552,12 @@ inline Vector4Base operator/(typename TOtherFloat::Type a, const Vector4Ba return Vector4Base(a) / b; } +template +inline uint32 GetHash(const Vector4Base& key) +{ + return (((((*(uint32*)&key.X * 397) ^ *(uint32*)&key.Y) * 397) ^ *(uint32*)&key.Z) * 397) ^*(uint32*)&key.W; +} + namespace Math { template From c27187bd0ab0a5c5065d1e05df0772264062588e Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 25 Oct 2023 19:17:04 +0200 Subject: [PATCH 53/78] Fix deserializing vector types in Editor from `ToString` FormatException #1802 --- Source/Editor/Options/InputBinding.cs | 7 ------ .../Math/TypeConverters/ColorConverter.cs | 4 ---- .../Math/TypeConverters/Double2Converter.cs | 24 ++----------------- .../Math/TypeConverters/Double3Converter.cs | 24 ++----------------- .../Math/TypeConverters/Double4Converter.cs | 24 ++----------------- .../Math/TypeConverters/Float2Converter.cs | 24 ++----------------- .../Math/TypeConverters/Float3Converter.cs | 24 ++----------------- .../Math/TypeConverters/Float4Converter.cs | 24 ++++++++++++++----- .../Core/Math/TypeConverters/Int2Converter.cs | 24 ++----------------- .../Core/Math/TypeConverters/Int3Converter.cs | 24 ++----------------- .../Core/Math/TypeConverters/Int4Converter.cs | 24 ++----------------- .../TypeConverters/QuaternionConverter.cs | 24 ++----------------- .../Math/TypeConverters/Vector2Converter.cs | 24 ++----------------- .../Math/TypeConverters/Vector3Converter.cs | 24 ++----------------- .../Math/TypeConverters/Vector4Converter.cs | 24 ++----------------- 15 files changed, 42 insertions(+), 281 deletions(-) diff --git a/Source/Editor/Options/InputBinding.cs b/Source/Editor/Options/InputBinding.cs index eb61c0f68..95c3c1d6f 100644 --- a/Source/Editor/Options/InputBinding.cs +++ b/Source/Editor/Options/InputBinding.cs @@ -259,10 +259,7 @@ namespace FlaxEditor.Options public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) { if (sourceType == typeof(string)) - { return true; - } - return base.CanConvertFrom(context, sourceType); } @@ -270,9 +267,7 @@ namespace FlaxEditor.Options public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) { if (destinationType == typeof(string)) - { return false; - } return base.CanConvertTo(context, destinationType); } @@ -284,7 +279,6 @@ namespace FlaxEditor.Options InputBinding.TryParse(str, out var result); return result; } - return base.ConvertFrom(context, culture, value); } @@ -295,7 +289,6 @@ namespace FlaxEditor.Options { return ((InputBinding)value).ToString(); } - return base.ConvertTo(context, culture, value, destinationType); } } diff --git a/Source/Engine/Core/Math/TypeConverters/ColorConverter.cs b/Source/Engine/Core/Math/TypeConverters/ColorConverter.cs index afd34bbfd..65377acaa 100644 --- a/Source/Engine/Core/Math/TypeConverters/ColorConverter.cs +++ b/Source/Engine/Core/Math/TypeConverters/ColorConverter.cs @@ -13,9 +13,7 @@ namespace FlaxEngine.TypeConverters public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) { if (sourceType == typeof(string)) - { return true; - } return base.CanConvertFrom(context, sourceType); } @@ -23,9 +21,7 @@ namespace FlaxEngine.TypeConverters public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) { if (destinationType == typeof(string)) - { return false; - } return base.CanConvertTo(context, destinationType); } diff --git a/Source/Engine/Core/Math/TypeConverters/Double2Converter.cs b/Source/Engine/Core/Math/TypeConverters/Double2Converter.cs index 4eebbfce4..e0670df05 100644 --- a/Source/Engine/Core/Math/TypeConverters/Double2Converter.cs +++ b/Source/Engine/Core/Math/TypeConverters/Double2Converter.cs @@ -7,34 +7,14 @@ using System.Globalization; namespace FlaxEngine.TypeConverters { - internal class Double2Converter : TypeConverter + internal class Double2Converter : VectorConverter { - /// - public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) - { - if (sourceType == typeof(string)) - { - return true; - } - return base.CanConvertFrom(context, sourceType); - } - - /// - public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) - { - if (destinationType == typeof(string)) - { - return false; - } - return base.CanConvertTo(context, destinationType); - } - /// public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { if (value is string str) { - string[] v = str.Split(','); + string[] v = GetParts(str); return new Double2(double.Parse(v[0], culture), double.Parse(v[1], culture)); } return base.ConvertFrom(context, culture, value); diff --git a/Source/Engine/Core/Math/TypeConverters/Double3Converter.cs b/Source/Engine/Core/Math/TypeConverters/Double3Converter.cs index 420e0016c..a66892ecb 100644 --- a/Source/Engine/Core/Math/TypeConverters/Double3Converter.cs +++ b/Source/Engine/Core/Math/TypeConverters/Double3Converter.cs @@ -7,34 +7,14 @@ using System.Globalization; namespace FlaxEngine.TypeConverters { - internal class Double3Converter : TypeConverter + internal class Double3Converter : VectorConverter { - /// - public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) - { - if (sourceType == typeof(string)) - { - return true; - } - return base.CanConvertFrom(context, sourceType); - } - - /// - public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) - { - if (destinationType == typeof(string)) - { - return false; - } - return base.CanConvertTo(context, destinationType); - } - /// public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { if (value is string str) { - string[] v = str.Split(','); + string[] v = GetParts(str); return new Double3(double.Parse(v[0], culture), double.Parse(v[1], culture), double.Parse(v[2], culture)); } return base.ConvertFrom(context, culture, value); diff --git a/Source/Engine/Core/Math/TypeConverters/Double4Converter.cs b/Source/Engine/Core/Math/TypeConverters/Double4Converter.cs index fc1d9a7fe..d085217ef 100644 --- a/Source/Engine/Core/Math/TypeConverters/Double4Converter.cs +++ b/Source/Engine/Core/Math/TypeConverters/Double4Converter.cs @@ -7,34 +7,14 @@ using System.Globalization; namespace FlaxEngine.TypeConverters { - internal class Double4Converter : TypeConverter + internal class Double4Converter : VectorConverter { - /// - public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) - { - if (sourceType == typeof(string)) - { - return true; - } - return base.CanConvertFrom(context, sourceType); - } - - /// - public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) - { - if (destinationType == typeof(string)) - { - return false; - } - return base.CanConvertTo(context, destinationType); - } - /// public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { if (value is string str) { - string[] v = str.Split(','); + string[] v = GetParts(str); return new Double4(double.Parse(v[0], culture), double.Parse(v[1], culture), double.Parse(v[2], culture), double.Parse(v[3], culture)); } return base.ConvertFrom(context, culture, value); diff --git a/Source/Engine/Core/Math/TypeConverters/Float2Converter.cs b/Source/Engine/Core/Math/TypeConverters/Float2Converter.cs index a41a0f4d5..4b2ffadf5 100644 --- a/Source/Engine/Core/Math/TypeConverters/Float2Converter.cs +++ b/Source/Engine/Core/Math/TypeConverters/Float2Converter.cs @@ -7,34 +7,14 @@ using System.Globalization; namespace FlaxEngine.TypeConverters { - internal class Float2Converter : TypeConverter + internal class Float2Converter : VectorConverter { - /// - public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) - { - if (sourceType == typeof(string)) - { - return true; - } - return base.CanConvertFrom(context, sourceType); - } - - /// - public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) - { - if (destinationType == typeof(string)) - { - return false; - } - return base.CanConvertTo(context, destinationType); - } - /// public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { if (value is string str) { - string[] v = str.Split(','); + string[] v = GetParts(str); return new Float2(float.Parse(v[0], culture), float.Parse(v[1], culture)); } return base.ConvertFrom(context, culture, value); diff --git a/Source/Engine/Core/Math/TypeConverters/Float3Converter.cs b/Source/Engine/Core/Math/TypeConverters/Float3Converter.cs index aded4117e..3739c44ef 100644 --- a/Source/Engine/Core/Math/TypeConverters/Float3Converter.cs +++ b/Source/Engine/Core/Math/TypeConverters/Float3Converter.cs @@ -7,34 +7,14 @@ using System.Globalization; namespace FlaxEngine.TypeConverters { - internal class Float3Converter : TypeConverter + internal class Float3Converter : VectorConverter { - /// - public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) - { - if (sourceType == typeof(string)) - { - return true; - } - return base.CanConvertFrom(context, sourceType); - } - - /// - public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) - { - if (destinationType == typeof(string)) - { - return false; - } - return base.CanConvertTo(context, destinationType); - } - /// public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { if (value is string str) { - string[] v = str.Split(','); + string[] v = GetParts(str); return new Float3(float.Parse(v[0], culture), float.Parse(v[1], culture), float.Parse(v[2], culture)); } return base.ConvertFrom(context, culture, value); diff --git a/Source/Engine/Core/Math/TypeConverters/Float4Converter.cs b/Source/Engine/Core/Math/TypeConverters/Float4Converter.cs index 58c76ac65..620f2c838 100644 --- a/Source/Engine/Core/Math/TypeConverters/Float4Converter.cs +++ b/Source/Engine/Core/Math/TypeConverters/Float4Converter.cs @@ -7,15 +7,13 @@ using System.Globalization; namespace FlaxEngine.TypeConverters { - internal class Float4Converter : TypeConverter + internal class VectorConverter : TypeConverter { /// public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) { if (sourceType == typeof(string)) - { return true; - } return base.CanConvertFrom(context, sourceType); } @@ -23,18 +21,32 @@ namespace FlaxEngine.TypeConverters public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) { if (destinationType == typeof(string)) - { return false; - } return base.CanConvertTo(context, destinationType); } + internal static string[] GetParts(string str) + { + string[] v = str.Split(','); + if (v.Length == 1) + { + // When converting from ToString() + v = str.Split(' '); + for (int i = 0; i < v.Length; i++) + v[i] = v[i].Substring(v[i].IndexOf(':') + 1); + } + return v; + } + } + + internal class Float4Converter : VectorConverter + { /// public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { if (value is string str) { - string[] v = str.Split(','); + string[] v = GetParts(str); return new Float4(float.Parse(v[0], culture), float.Parse(v[1], culture), float.Parse(v[2], culture), float.Parse(v[3], culture)); } return base.ConvertFrom(context, culture, value); diff --git a/Source/Engine/Core/Math/TypeConverters/Int2Converter.cs b/Source/Engine/Core/Math/TypeConverters/Int2Converter.cs index c4989c085..f528aa46b 100644 --- a/Source/Engine/Core/Math/TypeConverters/Int2Converter.cs +++ b/Source/Engine/Core/Math/TypeConverters/Int2Converter.cs @@ -7,34 +7,14 @@ using System.Globalization; namespace FlaxEngine.TypeConverters { - internal class Int2Converter : TypeConverter + internal class Int2Converter : VectorConverter { - /// - public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) - { - if (sourceType == typeof(string)) - { - return true; - } - return base.CanConvertFrom(context, sourceType); - } - - /// - public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) - { - if (destinationType == typeof(string)) - { - return false; - } - return base.CanConvertTo(context, destinationType); - } - /// public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { if (value is string str) { - string[] v = str.Split(','); + string[] v = GetParts(str); return new Int2(int.Parse(v[0], culture), int.Parse(v[1], culture)); } return base.ConvertFrom(context, culture, value); diff --git a/Source/Engine/Core/Math/TypeConverters/Int3Converter.cs b/Source/Engine/Core/Math/TypeConverters/Int3Converter.cs index fe01f91fd..520f806d0 100644 --- a/Source/Engine/Core/Math/TypeConverters/Int3Converter.cs +++ b/Source/Engine/Core/Math/TypeConverters/Int3Converter.cs @@ -7,34 +7,14 @@ using System.Globalization; namespace FlaxEngine.TypeConverters { - internal class Int3Converter : TypeConverter + internal class Int3Converter : VectorConverter { - /// - public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) - { - if (sourceType == typeof(string)) - { - return true; - } - return base.CanConvertFrom(context, sourceType); - } - - /// - public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) - { - if (destinationType == typeof(string)) - { - return false; - } - return base.CanConvertTo(context, destinationType); - } - /// public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { if (value is string str) { - string[] v = str.Split(','); + string[] v = GetParts(str); return new Int3(int.Parse(v[0], culture), int.Parse(v[1], culture), int.Parse(v[2], culture)); } return base.ConvertFrom(context, culture, value); diff --git a/Source/Engine/Core/Math/TypeConverters/Int4Converter.cs b/Source/Engine/Core/Math/TypeConverters/Int4Converter.cs index 2ce0fc202..e9a27dfda 100644 --- a/Source/Engine/Core/Math/TypeConverters/Int4Converter.cs +++ b/Source/Engine/Core/Math/TypeConverters/Int4Converter.cs @@ -7,34 +7,14 @@ using System.Globalization; namespace FlaxEngine.TypeConverters { - internal class Int4Converter : TypeConverter + internal class Int4Converter : VectorConverter { - /// - public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) - { - if (sourceType == typeof(string)) - { - return true; - } - return base.CanConvertFrom(context, sourceType); - } - - /// - public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) - { - if (destinationType == typeof(string)) - { - return false; - } - return base.CanConvertTo(context, destinationType); - } - /// public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { if (value is string str) { - string[] v = str.Split(','); + string[] v = GetParts(str); return new Int4(int.Parse(v[0], culture), int.Parse(v[1], culture), int.Parse(v[2], culture), int.Parse(v[3], culture)); } return base.ConvertFrom(context, culture, value); diff --git a/Source/Engine/Core/Math/TypeConverters/QuaternionConverter.cs b/Source/Engine/Core/Math/TypeConverters/QuaternionConverter.cs index 23bb901be..5d9aa206b 100644 --- a/Source/Engine/Core/Math/TypeConverters/QuaternionConverter.cs +++ b/Source/Engine/Core/Math/TypeConverters/QuaternionConverter.cs @@ -7,34 +7,14 @@ using System.Globalization; namespace FlaxEngine.TypeConverters { - internal class QuaternionConverter : TypeConverter + internal class QuaternionConverter : VectorConverter { - /// - public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) - { - if (sourceType == typeof(string)) - { - return true; - } - return base.CanConvertFrom(context, sourceType); - } - - /// - public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) - { - if (destinationType == typeof(string)) - { - return false; - } - return base.CanConvertTo(context, destinationType); - } - /// public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { if (value is string str) { - string[] v = str.Split(','); + string[] v = GetParts(str); return new Quaternion(float.Parse(v[0], culture), float.Parse(v[1], culture), float.Parse(v[2], culture), float.Parse(v[3], culture)); } return base.ConvertFrom(context, culture, value); diff --git a/Source/Engine/Core/Math/TypeConverters/Vector2Converter.cs b/Source/Engine/Core/Math/TypeConverters/Vector2Converter.cs index 96d6beadc..acb5b5817 100644 --- a/Source/Engine/Core/Math/TypeConverters/Vector2Converter.cs +++ b/Source/Engine/Core/Math/TypeConverters/Vector2Converter.cs @@ -7,34 +7,14 @@ using System.Globalization; namespace FlaxEngine.TypeConverters { - internal class Vector2Converter : TypeConverter + internal class Vector2Converter : VectorConverter { - /// - public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) - { - if (sourceType == typeof(string)) - { - return true; - } - return base.CanConvertFrom(context, sourceType); - } - - /// - public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) - { - if (destinationType == typeof(string)) - { - return false; - } - return base.CanConvertTo(context, destinationType); - } - /// public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { if (value is string str) { - string[] v = str.Split(','); + string[] v = GetParts(str); return new Vector2(float.Parse(v[0], culture), float.Parse(v[1], culture)); } return base.ConvertFrom(context, culture, value); diff --git a/Source/Engine/Core/Math/TypeConverters/Vector3Converter.cs b/Source/Engine/Core/Math/TypeConverters/Vector3Converter.cs index 23ee4df11..66ec831f0 100644 --- a/Source/Engine/Core/Math/TypeConverters/Vector3Converter.cs +++ b/Source/Engine/Core/Math/TypeConverters/Vector3Converter.cs @@ -7,34 +7,14 @@ using System.Globalization; namespace FlaxEngine.TypeConverters { - internal class Vector3Converter : TypeConverter + internal class Vector3Converter : VectorConverter { - /// - public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) - { - if (sourceType == typeof(string)) - { - return true; - } - return base.CanConvertFrom(context, sourceType); - } - - /// - public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) - { - if (destinationType == typeof(string)) - { - return false; - } - return base.CanConvertTo(context, destinationType); - } - /// public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { if (value is string str) { - string[] v = str.Split(','); + string[] v = GetParts(str); return new Vector3(float.Parse(v[0], culture), float.Parse(v[1], culture), float.Parse(v[2], culture)); } return base.ConvertFrom(context, culture, value); diff --git a/Source/Engine/Core/Math/TypeConverters/Vector4Converter.cs b/Source/Engine/Core/Math/TypeConverters/Vector4Converter.cs index c3b4d074b..f4781f45b 100644 --- a/Source/Engine/Core/Math/TypeConverters/Vector4Converter.cs +++ b/Source/Engine/Core/Math/TypeConverters/Vector4Converter.cs @@ -7,34 +7,14 @@ using System.Globalization; namespace FlaxEngine.TypeConverters { - internal class Vector4Converter : TypeConverter + internal class Vector4Converter : VectorConverter { - /// - public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) - { - if (sourceType == typeof(string)) - { - return true; - } - return base.CanConvertFrom(context, sourceType); - } - - /// - public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) - { - if (destinationType == typeof(string)) - { - return false; - } - return base.CanConvertTo(context, destinationType); - } - /// public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { if (value is string str) { - string[] v = str.Split(','); + string[] v = GetParts(str); return new Vector4(float.Parse(v[0], culture), float.Parse(v[1], culture), float.Parse(v[2], culture), float.Parse(v[3], culture)); } return base.ConvertFrom(context, culture, value); From 2158fa7358aca67c2a5c9f90c89edd4a92a2b52d Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 25 Oct 2023 23:34:29 +0200 Subject: [PATCH 54/78] Remove not needed end line in script template --- Content/Editor/Scripting/ScriptTemplate.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Content/Editor/Scripting/ScriptTemplate.cs b/Content/Editor/Scripting/ScriptTemplate.cs index 663acf05f..30fbe9d86 100644 --- a/Content/Editor/Scripting/ScriptTemplate.cs +++ b/Content/Editor/Scripting/ScriptTemplate.cs @@ -33,4 +33,3 @@ public class %class% : Script // Here you can add code that needs to be called every frame } } - From f44421b7a76551415e12915599387c862f7ecbb2 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 26 Oct 2023 11:25:30 +0200 Subject: [PATCH 55/78] Optimize managed method invoke on NetCore to skip virtual call that is the same as default one --- Source/Engine/Scripting/BinaryModule.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Source/Engine/Scripting/BinaryModule.cpp b/Source/Engine/Scripting/BinaryModule.cpp index efd147917..679ead4e3 100644 --- a/Source/Engine/Scripting/BinaryModule.cpp +++ b/Source/Engine/Scripting/BinaryModule.cpp @@ -1270,7 +1270,11 @@ bool ManagedBinaryModule::InvokeMethod(void* method, const Variant& instance, Sp // Invoke the method MObject* exception = nullptr; +#if USE_NETCORE // NetCore uses the same path for both virtual and non-virtual calls + MObject* resultObject = mMethod->Invoke(mInstance, params, &exception); +#else MObject* resultObject = withInterfaces ? mMethod->InvokeVirtual((MObject*)mInstance, params, &exception) : mMethod->Invoke(mInstance, params, &exception); +#endif if (exception) { MException ex(exception); From bfaa292b04ae6c793a0d9ab4e4b161cdc52a8687 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 26 Oct 2023 11:37:37 +0200 Subject: [PATCH 56/78] Fix invoking managed method on value types (eg. `Transform`) to properly handle instance value #1801 --- Source/Engine/Engine/NativeInterop.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Engine/NativeInterop.cs b/Source/Engine/Engine/NativeInterop.cs index 2f74e421c..fea1fadda 100644 --- a/Source/Engine/Engine/NativeInterop.cs +++ b/Source/Engine/Engine/NativeInterop.cs @@ -1302,7 +1302,8 @@ namespace FlaxEngine.Interop #if !USE_AOT internal bool TryGetDelegate(out Invoker.MarshalAndInvokeDelegate outDeleg, out object outDelegInvoke) { - if (invokeDelegate == null) + // Skip using in-built delegate for value types (eg. Transform) to properly handle instance value passing to method + if (invokeDelegate == null && !method.DeclaringType.IsValueType) { List methodTypes = new List(); if (!method.IsStatic) From 186e13b5e894d898db4ad8bf3fae70df5f3cbed1 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 26 Oct 2023 14:33:21 +0200 Subject: [PATCH 57/78] Add support for runtime running on GPU without Compute Shaders support --- Source/Engine/Graphics/GPUDevice.cpp | 2 ++ Source/Engine/Graphics/Shaders/GPUShader.cpp | 11 ++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Graphics/GPUDevice.cpp b/Source/Engine/Graphics/GPUDevice.cpp index 34d8e7661..f0056e26c 100644 --- a/Source/Engine/Graphics/GPUDevice.cpp +++ b/Source/Engine/Graphics/GPUDevice.cpp @@ -313,6 +313,8 @@ bool GPUDevice::Init() _res->TasksManager.SetExecutor(CreateTasksExecutor()); LOG(Info, "Total graphics memory: {0}", Utilities::BytesToText(TotalGraphicsMemory)); + if (!Limits.HasCompute) + LOG(Warning, "Compute Shaders are not supported"); return false; } diff --git a/Source/Engine/Graphics/Shaders/GPUShader.cpp b/Source/Engine/Graphics/Shaders/GPUShader.cpp index e76ee9996..86d983fd8 100644 --- a/Source/Engine/Graphics/Shaders/GPUShader.cpp +++ b/Source/Engine/Graphics/Shaders/GPUShader.cpp @@ -87,7 +87,11 @@ bool GPUShader::Create(MemoryReadStream& stream) GPUShaderProgramInitializer initializer; #if !BUILD_RELEASE initializer.Owner = this; + const StringView name = GetName(); +#else + const StringView name; #endif + const bool hasCompute = GPUDevice::Instance->Limits.HasCompute; for (int32 i = 0; i < shadersCount; i++) { const ShaderStage type = static_cast(stream.ReadByte()); @@ -117,10 +121,15 @@ bool GPUShader::Create(MemoryReadStream& stream) stream.ReadBytes(&initializer.Bindings, sizeof(ShaderBindings)); // Create shader program + if (type == ShaderStage::Compute && !hasCompute) + { + LOG(Warning, "Failed to create {} Shader program '{}' ({}).", ::ToString(type), String(initializer.Name), name); + continue; + } GPUShaderProgram* shader = CreateGPUShaderProgram(type, initializer, cache, cacheSize, stream); if (shader == nullptr) { - LOG(Error, "Failed to create {} Shader program '{}'.", ::ToString(type), String(initializer.Name)); + LOG(Error, "Failed to create {} Shader program '{}' ({}).", ::ToString(type), String(initializer.Name), name); return true; } From 1d41aa01ce95d942148deb7b97132872bbc8db9d Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 26 Oct 2023 14:36:02 +0200 Subject: [PATCH 58/78] Refactor vertex shaders to use `GPUShaderProgramVS::InputElement` for input layout data --- .../Graphics/Shaders/GPUShaderProgram.h | 13 +++++++ .../DirectX/DX11/GPUShaderDX11.cpp | 35 ++++++----------- .../DirectX/DX12/GPUShaderDX12.cpp | 33 ++++++---------- .../GraphicsDevice/Vulkan/GPUShaderVulkan.cpp | 38 +++++++------------ .../ShadersCompilation/ShaderCompiler.cpp | 19 +++++----- 5 files changed, 59 insertions(+), 79 deletions(-) diff --git a/Source/Engine/Graphics/Shaders/GPUShaderProgram.h b/Source/Engine/Graphics/Shaders/GPUShaderProgram.h index 09c45506f..31f6638dc 100644 --- a/Source/Engine/Graphics/Shaders/GPUShaderProgram.h +++ b/Source/Engine/Graphics/Shaders/GPUShaderProgram.h @@ -122,6 +122,19 @@ public: /// class GPUShaderProgramVS : public GPUShaderProgram { +public: + // Input element run-time data (see VertexShaderMeta::InputElement for compile-time data) + PACK_STRUCT(struct InputElement + { + byte Type; // VertexShaderMeta::InputType + byte Index; + byte Format; // PixelFormat + byte InputSlot; + uint32 AlignedByteOffset; // Fixed value or INPUT_LAYOUT_ELEMENT_ALIGN if auto + byte InputSlotClass; // INPUT_LAYOUT_ELEMENT_PER_VERTEX_DATA or INPUT_LAYOUT_ELEMENT_PER_INSTANCE_DATA + uint32 InstanceDataStepRate; // 0 if per-vertex + }); + public: /// /// Gets input layout description handle (platform dependent). diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUShaderDX11.cpp b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUShaderDX11.cpp index b58684a4e..52192c634 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUShaderDX11.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUShaderDX11.cpp @@ -15,32 +15,21 @@ GPUShaderProgram* GPUShaderDX11::CreateGPUShaderProgram(ShaderStage type, const { case ShaderStage::Vertex: { - D3D11_INPUT_ELEMENT_DESC inputLayoutDesc[VERTEX_SHADER_MAX_INPUT_ELEMENTS]; - - // Temporary variables - byte Type, Format, Index, InputSlot, InputSlotClass; - uint32 AlignedByteOffset, InstanceDataStepRate; - - // Load Input Layout (it may be empty) + // Load Input Layout byte inputLayoutSize; stream.ReadByte(&inputLayoutSize); ASSERT(inputLayoutSize <= VERTEX_SHADER_MAX_INPUT_ELEMENTS); + D3D11_INPUT_ELEMENT_DESC inputLayoutDesc[VERTEX_SHADER_MAX_INPUT_ELEMENTS]; for (int32 a = 0; a < inputLayoutSize; a++) { // Read description - // TODO: maybe use struct and load at once? - stream.ReadByte(&Type); - stream.ReadByte(&Index); - stream.ReadByte(&Format); - stream.ReadByte(&InputSlot); - stream.ReadUint32(&AlignedByteOffset); - stream.ReadByte(&InputSlotClass); - stream.ReadUint32(&InstanceDataStepRate); + GPUShaderProgramVS::InputElement inputElement; + stream.Read(inputElement); // Get semantic name const char* semanticName = nullptr; // TODO: maybe use enum+mapping ? - switch (Type) + switch (inputElement.Type) { case 1: semanticName = "POSITION"; @@ -70,7 +59,7 @@ GPUShaderProgram* GPUShaderDX11::CreateGPUShaderProgram(ShaderStage type, const semanticName = "BLENDWEIGHT"; break; default: - LOG(Fatal, "Invalid vertex shader element semantic type: {0}", Type); + LOG(Fatal, "Invalid vertex shader element semantic type: {0}", inputElement.Type); break; } @@ -78,12 +67,12 @@ GPUShaderProgram* GPUShaderDX11::CreateGPUShaderProgram(ShaderStage type, const inputLayoutDesc[a] = { semanticName, - static_cast(Index), - static_cast(Format), - static_cast(InputSlot), - static_cast(AlignedByteOffset), - static_cast(InputSlotClass), - static_cast(InstanceDataStepRate) + static_cast(inputElement.Index), + static_cast(inputElement.Format), + static_cast(inputElement.InputSlot), + static_cast(inputElement.AlignedByteOffset), + static_cast(inputElement.InputSlotClass), + static_cast(inputElement.InstanceDataStepRate) }; } diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUShaderDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUShaderDX12.cpp index e1e716853..07352b674 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUShaderDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUShaderDX12.cpp @@ -20,32 +20,21 @@ GPUShaderProgram* GPUShaderDX12::CreateGPUShaderProgram(ShaderStage type, const { case ShaderStage::Vertex: { - D3D12_INPUT_ELEMENT_DESC inputLayout[VERTEX_SHADER_MAX_INPUT_ELEMENTS]; - - // Temporary variables - byte Type, Format, Index, InputSlot, InputSlotClass; - uint32 AlignedByteOffset, InstanceDataStepRate; - // Load Input Layout (it may be empty) byte inputLayoutSize; stream.ReadByte(&inputLayoutSize); ASSERT(inputLayoutSize <= VERTEX_SHADER_MAX_INPUT_ELEMENTS); + D3D12_INPUT_ELEMENT_DESC inputLayout[VERTEX_SHADER_MAX_INPUT_ELEMENTS]; for (int32 a = 0; a < inputLayoutSize; a++) { // Read description - // TODO: maybe use struct and load at once? - stream.ReadByte(&Type); - stream.ReadByte(&Index); - stream.ReadByte(&Format); - stream.ReadByte(&InputSlot); - stream.ReadUint32(&AlignedByteOffset); - stream.ReadByte(&InputSlotClass); - stream.ReadUint32(&InstanceDataStepRate); + GPUShaderProgramVS::InputElement inputElement; + stream.Read(inputElement); // Get semantic name const char* semanticName = nullptr; // TODO: maybe use enum+mapping ? - switch (Type) + switch (inputElement.Type) { case 1: semanticName = "POSITION"; @@ -75,7 +64,7 @@ GPUShaderProgram* GPUShaderDX12::CreateGPUShaderProgram(ShaderStage type, const semanticName = "BLENDWEIGHT"; break; default: - LOG(Fatal, "Invalid vertex shader element semantic type: {0}", Type); + LOG(Fatal, "Invalid vertex shader element semantic type: {0}", inputElement.Type); break; } @@ -83,12 +72,12 @@ GPUShaderProgram* GPUShaderDX12::CreateGPUShaderProgram(ShaderStage type, const inputLayout[a] = { semanticName, - static_cast(Index), - static_cast(Format), - static_cast(InputSlot), - static_cast(AlignedByteOffset), - static_cast(InputSlotClass), - static_cast(InstanceDataStepRate) + static_cast(inputElement.Index), + static_cast(inputElement.Format), + static_cast(inputElement.InputSlot), + static_cast(inputElement.AlignedByteOffset), + static_cast(inputElement.InputSlotClass), + static_cast(inputElement.InstanceDataStepRate) }; } diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUShaderVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUShaderVulkan.cpp index 5e583d713..852ce5bad 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUShaderVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUShaderVulkan.cpp @@ -14,9 +14,9 @@ #include "Engine/Graphics/PixelFormatExtensions.h" #if PLATFORM_DESKTOP -#define VULKAN_UNIFORM_RING_BUFFER_SIZE 24 * 1024 * 1024 +#define VULKAN_UNIFORM_RING_BUFFER_SIZE (24 * 1024 * 1024) #else -#define VULKAN_UNIFORM_RING_BUFFER_SIZE 8 * 1024 * 1024 +#define VULKAN_UNIFORM_RING_BUFFER_SIZE (8 * 1024 * 1024) #endif UniformBufferUploaderVulkan::UniformBufferUploaderVulkan(GPUDeviceVulkan* device) @@ -153,10 +153,6 @@ GPUShaderProgram* GPUShaderVulkan::CreateGPUShaderProgram(ShaderStage type, cons vertexBindingDescriptions[i].inputRate = VK_VERTEX_INPUT_RATE_VERTEX; } - // Temporary variables - byte Type, Format, Index, InputSlot, InputSlotClass; - uint32 AlignedByteOffset, InstanceDataStepRate; - // Load Input Layout (it may be empty) byte inputLayoutSize; stream.ReadByte(&inputLayoutSize); @@ -167,32 +163,26 @@ GPUShaderProgram* GPUShaderVulkan::CreateGPUShaderProgram(ShaderStage type, cons for (int32 a = 0; a < inputLayoutSize; a++) { // Read description - // TODO: maybe use struct and load at once? - stream.ReadByte(&Type); - stream.ReadByte(&Index); - stream.ReadByte(&Format); - stream.ReadByte(&InputSlot); - stream.ReadUint32(&AlignedByteOffset); - stream.ReadByte(&InputSlotClass); - stream.ReadUint32(&InstanceDataStepRate); + GPUShaderProgramVS::InputElement inputElement; + stream.Read(inputElement); - const auto size = PixelFormatExtensions::SizeInBytes((PixelFormat)Format); - if (AlignedByteOffset != INPUT_LAYOUT_ELEMENT_ALIGN) - offset = AlignedByteOffset; + const auto size = PixelFormatExtensions::SizeInBytes((PixelFormat)inputElement.Format); + if (inputElement.AlignedByteOffset != INPUT_LAYOUT_ELEMENT_ALIGN) + offset = inputElement.AlignedByteOffset; - auto& vertexBindingDescription = vertexBindingDescriptions[InputSlot]; - vertexBindingDescription.binding = InputSlot; + auto& vertexBindingDescription = vertexBindingDescriptions[inputElement.InputSlot]; + vertexBindingDescription.binding = inputElement.InputSlot; vertexBindingDescription.stride = Math::Max(vertexBindingDescription.stride, (uint32_t)(offset + size)); - vertexBindingDescription.inputRate = InputSlotClass == INPUT_LAYOUT_ELEMENT_PER_VERTEX_DATA ? VK_VERTEX_INPUT_RATE_VERTEX : VK_VERTEX_INPUT_RATE_INSTANCE; - ASSERT(InstanceDataStepRate == 0 || InstanceDataStepRate == 1); + vertexBindingDescription.inputRate = inputElement.InputSlotClass == INPUT_LAYOUT_ELEMENT_PER_VERTEX_DATA ? VK_VERTEX_INPUT_RATE_VERTEX : VK_VERTEX_INPUT_RATE_INSTANCE; + ASSERT(inputElement.InstanceDataStepRate == 0 || inputElement.InstanceDataStepRate == 1); auto& vertexAttributeDescription = vertexAttributeDescriptions[a]; vertexAttributeDescription.location = a; - vertexAttributeDescription.binding = InputSlot; - vertexAttributeDescription.format = RenderToolsVulkan::ToVulkanFormat((PixelFormat)Format); + vertexAttributeDescription.binding = inputElement.InputSlot; + vertexAttributeDescription.format = RenderToolsVulkan::ToVulkanFormat((PixelFormat)inputElement.Format); vertexAttributeDescription.offset = offset; - bindingsCount = Math::Max(bindingsCount, (uint32)InputSlot + 1); + bindingsCount = Math::Max(bindingsCount, (uint32)inputElement.InputSlot + 1); offset += size; } diff --git a/Source/Engine/ShadersCompilation/ShaderCompiler.cpp b/Source/Engine/ShadersCompilation/ShaderCompiler.cpp index 291910b11..c0fda4d6e 100644 --- a/Source/Engine/ShadersCompilation/ShaderCompiler.cpp +++ b/Source/Engine/ShadersCompilation/ShaderCompiler.cpp @@ -460,16 +460,15 @@ bool ShaderCompiler::WriteCustomDataVS(ShaderCompilationContext* context, Shader auto& element = layout[a]; if (!layoutVisible[a]) continue; - - // TODO: serialize whole struct? - - output->WriteByte(static_cast(element.Type)); - output->WriteByte(element.Index); - output->WriteByte(static_cast(element.Format)); - output->WriteByte(element.InputSlot); - output->WriteUint32(element.AlignedByteOffset); - output->WriteByte(element.InputSlotClass); - output->WriteUint32(element.InstanceDataStepRate); + GPUShaderProgramVS::InputElement data; + data.Type = static_cast(element.Type); + data.Index = element.Index; + data.Format = static_cast(element.Format); + data.InputSlot = element.InputSlot; + data.AlignedByteOffset = element.AlignedByteOffset; + data.InputSlotClass = element.InputSlotClass; + data.InstanceDataStepRate = element.InstanceDataStepRate; + output->Write(data); } return false; From d9b0e99b9c46e5370f79ad810beb21b709a40815 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 26 Oct 2023 15:20:35 +0200 Subject: [PATCH 59/78] Add support for using `Revision` number in projects version --- Source/Editor/ProjectInfo.cpp | 3 +- Source/Editor/ProjectInfo.cs | 85 ++++++++--------- Source/Engine/Core/Types/Version.cpp | 6 +- .../Bindings/BindingsGenerator.Cpp.cs | 7 +- Source/Tools/Flax.Build/ProjectInfo.cs | 93 +++++++++---------- 5 files changed, 91 insertions(+), 103 deletions(-) diff --git a/Source/Editor/ProjectInfo.cpp b/Source/Editor/ProjectInfo.cpp index 4e7ee4483..30c558b5d 100644 --- a/Source/Editor/ProjectInfo.cpp +++ b/Source/Editor/ProjectInfo.cpp @@ -154,7 +154,8 @@ bool ProjectInfo::LoadProject(const String& projectPath) Version = ::Version( JsonTools::GetInt(version, "Major", 0), JsonTools::GetInt(version, "Minor", 0), - JsonTools::GetInt(version, "Build", 0)); + JsonTools::GetInt(version, "Build", -1), + JsonTools::GetInt(version, "Revision", -1)); } } if (Version.Revision() == 0) diff --git a/Source/Editor/ProjectInfo.cs b/Source/Editor/ProjectInfo.cs index b00c4e042..083665f0f 100644 --- a/Source/Editor/ProjectInfo.cs +++ b/Source/Editor/ProjectInfo.cs @@ -23,17 +23,11 @@ namespace FlaxEditor public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { if (value == null) - { writer.WriteNull(); - } else if (value is Version) - { writer.WriteValue(value.ToString()); - } else - { throw new JsonSerializationException("Expected Version object value"); - } } /// @@ -47,65 +41,60 @@ namespace FlaxEditor public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { if (reader.TokenType == JsonToken.Null) - { return null; - } - else + + if (reader.TokenType == JsonToken.StartObject) { - if (reader.TokenType == JsonToken.StartObject) + try { - try + reader.Read(); + var values = new Dictionary(); + while (reader.TokenType == JsonToken.PropertyName) { + var key = reader.Value as string; reader.Read(); - Dictionary values = new Dictionary(); - while (reader.TokenType == JsonToken.PropertyName) - { - var key = reader.Value as string; - reader.Read(); - var val = (long)reader.Value; - reader.Read(); - values.Add(key, (int)val); - } + var val = (long)reader.Value; + reader.Read(); + values.Add(key, (int)val); + } - int major = 0, minor = 0, build = 0; - values.TryGetValue("Major", out major); - values.TryGetValue("Minor", out minor); - values.TryGetValue("Build", out build); + values.TryGetValue("Major", out var major); + values.TryGetValue("Minor", out var minor); + if (!values.TryGetValue("Build", out var build)) + build = -1; + if (!values.TryGetValue("Revision", out var revision)) + revision = -1; - Version v = new Version(major, minor, build); - return v; - } - catch (Exception ex) - { - throw new Exception(String.Format("Error parsing version string: {0}", reader.Value), ex); - } + if (build <= 0) + return new Version(major, minor); + if (revision <= 0) + return new Version(major, minor, build); + return new Version(major, minor, build, revision); } - else if (reader.TokenType == JsonToken.String) + catch (Exception ex) { - try - { - Version v = new Version((string)reader.Value!); - return v; - } - catch (Exception ex) - { - throw new Exception(String.Format("Error parsing version string: {0}", reader.Value), ex); - } - } - else - { - throw new Exception(String.Format("Unexpected token or value when parsing version. Token: {0}, Value: {1}", reader.TokenType, reader.Value)); + throw new Exception(String.Format("Error parsing version string: {0}", reader.Value), ex); } } + if (reader.TokenType == JsonToken.String) + { + try + { + return new Version((string)reader.Value!); + } + catch (Exception ex) + { + throw new Exception(String.Format("Error parsing version string: {0}", reader.Value), ex); + } + } + throw new Exception(String.Format("Unexpected token or value when parsing version. Token: {0}, Value: {1}", reader.TokenType, reader.Value)); } /// /// Determines whether this instance can convert the specified object type. /// /// Type of the object. - /// - /// true if this instance can convert the specified object type; otherwise, false. - /// + /// true if this instance can convert the specified object type; otherwise, false. public override bool CanConvert(Type objectType) { return objectType == typeof(Version); diff --git a/Source/Engine/Core/Types/Version.cpp b/Source/Engine/Core/Types/Version.cpp index 4a11a0af6..c0410d1cb 100644 --- a/Source/Engine/Core/Types/Version.cpp +++ b/Source/Engine/Core/Types/Version.cpp @@ -7,15 +7,15 @@ Version::Version(int32 major, int32 minor, int32 build, int32 revision) { _major = Math::Max(major, 0); _minor = Math::Max(minor, 0); - _build = Math::Max(build, 0); - _revision = Math::Max(revision, 0); + _build = Math::Max(build, -1); + _revision = Math::Max(revision, -1); } Version::Version(int32 major, int32 minor, int32 build) { _major = Math::Max(major, 0); _minor = Math::Max(minor, 0); - _build = Math::Max(build, 0); + _build = Math::Max(build, -1); _revision = -1; } diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs index 90e96d075..10e4d5846 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs @@ -3135,14 +3135,17 @@ namespace Flax.Build.Bindings contents.AppendLine("#pragma once"); contents.AppendLine(); contents.AppendLine($"#define {binaryModuleNameUpper}_NAME \"{binaryModuleName}\""); - if (version.Build == -1) + if (version.Build <= 0) contents.AppendLine($"#define {binaryModuleNameUpper}_VERSION Version({version.Major}, {version.Minor})"); - else + else if (version.Revision <= 0) contents.AppendLine($"#define {binaryModuleNameUpper}_VERSION Version({version.Major}, {version.Minor}, {version.Build})"); + else + contents.AppendLine($"#define {binaryModuleNameUpper}_VERSION Version({version.Major}, {version.Minor}, {version.Build}, {version.Revision})"); contents.AppendLine($"#define {binaryModuleNameUpper}_VERSION_TEXT \"{version}\""); contents.AppendLine($"#define {binaryModuleNameUpper}_VERSION_MAJOR {version.Major}"); contents.AppendLine($"#define {binaryModuleNameUpper}_VERSION_MINOR {version.Minor}"); contents.AppendLine($"#define {binaryModuleNameUpper}_VERSION_BUILD {version.Build}"); + contents.AppendLine($"#define {binaryModuleNameUpper}_VERSION_REVISION {version.Revision}"); contents.AppendLine($"#define {binaryModuleNameUpper}_COMPANY \"{project.Company}\""); contents.AppendLine($"#define {binaryModuleNameUpper}_COPYRIGHT \"{project.Copyright}\""); contents.AppendLine(); diff --git a/Source/Tools/Flax.Build/ProjectInfo.cs b/Source/Tools/Flax.Build/ProjectInfo.cs index 7830f59c1..ec615fc49 100644 --- a/Source/Tools/Flax.Build/ProjectInfo.cs +++ b/Source/Tools/Flax.Build/ProjectInfo.cs @@ -14,9 +14,9 @@ namespace Flax.Build /// /// Writes the JSON representation of the object. /// - /// The to write to. + /// The to write to. /// The value. - /// The calling serializer. + /// The calling serializer. public override void Write(Utf8JsonWriter writer, Version value, JsonSerializerOptions options) { writer.WriteStringValue(value.ToString()); @@ -25,73 +25,68 @@ namespace Flax.Build /// /// Reads the JSON representation of the object. /// - /// The to read from. - /// Type of the object. - /// The existing property value of the JSON that is being converted. - /// The calling serializer. + /// The to read from. + /// Type of the object. + /// The serializer options. /// The object value. public override Version? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { if (reader.TokenType == JsonTokenType.Null) - { return null; - } - else + + if (reader.TokenType == JsonTokenType.StartObject) { - if (reader.TokenType == JsonTokenType.StartObject) + try { - try + reader.Read(); + var values = new Dictionary(); + while (reader.TokenType == JsonTokenType.PropertyName) { + var key = reader.GetString(); reader.Read(); - Dictionary values = new Dictionary(); - while (reader.TokenType == JsonTokenType.PropertyName) - { - var key = reader.GetString(); - reader.Read(); - var val = reader.GetInt32(); - reader.Read(); - values.Add(key, val); - } + var val = reader.GetInt32(); + reader.Read(); + values.Add(key, val); + } - int major = 0, minor = 0, build = 0; - values.TryGetValue("Major", out major); - values.TryGetValue("Minor", out minor); - values.TryGetValue("Build", out build); + values.TryGetValue("Major", out var major); + values.TryGetValue("Minor", out var minor); + if (!values.TryGetValue("Build", out var build)) + build = -1; + if (!values.TryGetValue("Revision", out var revision)) + revision = -1; - Version v = new Version(major, minor, build); - return v; - } - catch (Exception ex) - { - throw new Exception(String.Format("Error parsing version string: {0}", reader.GetString()), ex); - } + if (build <= 0) + return new Version(major, minor); + if (revision <= 0) + return new Version(major, minor, build); + return new Version(major, minor, build, revision); } - else if (reader.TokenType == JsonTokenType.String) + catch (Exception ex) { - try - { - Version v = new Version((string)reader.GetString()!); - return v; - } - catch (Exception ex) - { - throw new Exception(String.Format("Error parsing version string: {0}", reader.GetString()), ex); - } - } - else - { - throw new Exception(String.Format("Unexpected token or value when parsing version. Token: {0}, Value: {1}", reader.TokenType, reader.GetString())); + throw new Exception(string.Format("Error parsing version string: {0}", reader.GetString()), ex); } } + + if (reader.TokenType == JsonTokenType.String) + { + try + { + return new Version((string)reader.GetString()!); + } + catch (Exception ex) + { + throw new Exception(string.Format("Error parsing version string: {0}", reader.GetString()), ex); + } + } + throw new Exception(string.Format("Unexpected token or value when parsing version. Token: {0}, Value: {1}", reader.TokenType, reader.GetString())); } /// /// Determines whether this instance can convert the specified object type. /// /// Type of the object. - /// - /// true if this instance can convert the specified object type; otherwise, false. - /// + /// true if this instance can convert the specified object type; otherwise, false. public override bool CanConvert(Type objectType) { return objectType == typeof(Version); @@ -318,7 +313,7 @@ namespace Flax.Build Log.Verbose("Loading project file from \"" + path + "\"..."); var contents = File.ReadAllText(path); var project = JsonSerializer.Deserialize(contents.AsSpan(), - new JsonSerializerOptions() { Converters = { new FlaxVersionConverter() }, IncludeFields = true, TypeInfoResolver = ProjectInfoSourceGenerationContext.Default }); + new JsonSerializerOptions() { Converters = { new FlaxVersionConverter() }, IncludeFields = true, TypeInfoResolver = ProjectInfoSourceGenerationContext.Default }); project.ProjectPath = path; project.ProjectFolderPath = Path.GetDirectoryName(path); From 70a06c5db362624f51358d6e0afc8f76b0197ba5 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 26 Oct 2023 15:21:01 +0200 Subject: [PATCH 60/78] Update build number and add revision field for future patches --- Flax.flaxproj | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Flax.flaxproj b/Flax.flaxproj index 6b3014e94..78d9714f5 100644 --- a/Flax.flaxproj +++ b/Flax.flaxproj @@ -3,7 +3,8 @@ "Version": { "Major": 1, "Minor": 7, - "Build": 6402 + "Revision": 0, + "Build": 6403 }, "Company": "Flax", "Copyright": "Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.", From 615b6470e53a4643230fd51aee70454958544706 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 28 Oct 2023 11:44:11 +0200 Subject: [PATCH 61/78] Fix iOS project path if it contains whitespace chars --- .../iOS/Binaries/Project/FlaxGame.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Platforms/iOS/Binaries/Project/FlaxGame.xcodeproj/project.pbxproj b/Source/Platforms/iOS/Binaries/Project/FlaxGame.xcodeproj/project.pbxproj index 1dd5d4e01..3b8889487 100644 --- a/Source/Platforms/iOS/Binaries/Project/FlaxGame.xcodeproj/project.pbxproj +++ b/Source/Platforms/iOS/Binaries/Project/FlaxGame.xcodeproj/project.pbxproj @@ -222,7 +222,7 @@ ${PBXResourcesGroup} GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - HEADER_SEARCH_PATHS = ${HeaderSearchPaths}; + HEADER_SEARCH_PATHS = "${HeaderSearchPaths}"; IPHONEOS_DEPLOYMENT_TARGET = 14.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; @@ -275,7 +275,7 @@ ${PBXResourcesGroup} GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - HEADER_SEARCH_PATHS = ${HeaderSearchPaths}; + HEADER_SEARCH_PATHS = "${HeaderSearchPaths}"; IPHONEOS_DEPLOYMENT_TARGET = 14.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; From a95c9059aa09501503ea7e6f32b8b94bbf2663d9 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 27 Oct 2023 10:08:04 +0200 Subject: [PATCH 62/78] Update build number --- Flax.flaxproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flax.flaxproj b/Flax.flaxproj index 78d9714f5..a3157a032 100644 --- a/Flax.flaxproj +++ b/Flax.flaxproj @@ -4,7 +4,7 @@ "Major": 1, "Minor": 7, "Revision": 0, - "Build": 6403 + "Build": 6404 }, "Company": "Flax", "Copyright": "Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.", From b028faf0ccdc35df62ce9d9f98e388eec9cf71b7 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 28 Oct 2023 13:52:23 +0200 Subject: [PATCH 63/78] Update assets --- Content/Editor/Particles/Smoke.flax | 4 ++-- Content/Editor/Particles/Sparks.flax | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Content/Editor/Particles/Smoke.flax b/Content/Editor/Particles/Smoke.flax index 1335a84f4..b42c2f325 100644 --- a/Content/Editor/Particles/Smoke.flax +++ b/Content/Editor/Particles/Smoke.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c3dc51e7805056006ca6cbb481ba202583a9b2287c152fc04e28e1d07747d6ce -size 14706 +oid sha256:334ac0d00495fc88b10839061ff0c3f45323d4f75ab6176b19005199a7324a19 +size 14569 diff --git a/Content/Editor/Particles/Sparks.flax b/Content/Editor/Particles/Sparks.flax index 7977e231b..7ee0ed6e9 100644 --- a/Content/Editor/Particles/Sparks.flax +++ b/Content/Editor/Particles/Sparks.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:77d902ab5f79426cc66dc5f19a3b8280136a58aa3c6fd317554d1a032357c65a -size 15275 +oid sha256:87046a9bfe275cac290b4764de8a512c222ccc386d01af9026d57c3e4b7773b6 +size 13625 From 468c93949ece14505fcd643350112290198da936 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 28 Oct 2023 14:01:44 +0200 Subject: [PATCH 64/78] Fix crash when creating empty particle emitter --- Source/Engine/Particles/Particles.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Particles/Particles.cpp b/Source/Engine/Particles/Particles.cpp index 52845a0f8..784f28c98 100644 --- a/Source/Engine/Particles/Particles.cpp +++ b/Source/Engine/Particles/Particles.cpp @@ -1334,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 From 9f3221c533cd75716b2fbe4b0ce9ab20f6f1e959 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 28 Oct 2023 14:02:25 +0200 Subject: [PATCH 65/78] Remove unused include --- Source/Engine/Physics/Actors/PhysicsColliderActor.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/Source/Engine/Physics/Actors/PhysicsColliderActor.cpp b/Source/Engine/Physics/Actors/PhysicsColliderActor.cpp index 02645943c..f2ab5d8a2 100644 --- a/Source/Engine/Physics/Actors/PhysicsColliderActor.cpp +++ b/Source/Engine/Physics/Actors/PhysicsColliderActor.cpp @@ -1,7 +1,6 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. #include "PhysicsColliderActor.h" -#include "Engine/Scripting/Script.h" #include "RigidBody.h" PhysicsColliderActor::PhysicsColliderActor(const SpawnParams& params) From d9c2decff50f8c5e2658a151624160505cb43d2c Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 28 Oct 2023 20:09:27 +0200 Subject: [PATCH 66/78] Fix crash when contact was not read properly --- Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.cpp b/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.cpp index 684aeca19..5968bdd78 100644 --- a/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.cpp +++ b/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.cpp @@ -117,9 +117,7 @@ void SimulationEventCallback::onContact(const PxContactPairHeader& pairHeader, c { // Skip sending events to removed actors if (pairHeader.flags & (PxContactPairHeaderFlag::eREMOVED_ACTOR_0 | PxContactPairHeaderFlag::eREMOVED_ACTOR_1)) - { return; - } Collision c; PxContactPairExtraDataIterator j(pairHeader.extraDataStream, pairHeader.extraDataStreamSize); @@ -193,7 +191,7 @@ void SimulationEventCallback::onContact(const PxContactPairHeader& pairHeader, c RemovedCollisions.Add(c); } } - ASSERT(!j.nextItemSet()); + //ASSERT(!j.nextItemSet()); } void SimulationEventCallback::onTrigger(PxTriggerPair* pairs, PxU32 count) From 9c1a7a20d490ee3cf561dd4f9b6dab919595e045 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 28 Oct 2023 22:10:41 +0200 Subject: [PATCH 67/78] Add `volk.h` header to distributed build files --- Source/ThirdParty/volk/volk.Build.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Source/ThirdParty/volk/volk.Build.cs b/Source/ThirdParty/volk/volk.Build.cs index 2f4cfa0d6..67f7593d9 100644 --- a/Source/ThirdParty/volk/volk.Build.cs +++ b/Source/ThirdParty/volk/volk.Build.cs @@ -1,5 +1,6 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. +using System.Collections.Generic; using System.IO; using Flax.Build; using Flax.Build.NativeCpp; @@ -62,4 +63,12 @@ public class volk : ThirdPartyModule Log.ErrorOnce("Missing VulkanSDK.", ref _missingSDKError); } } + + /// + public override void GetFilesToDeploy(List files) + { + base.GetFilesToDeploy(files); + + files.Add(Path.Combine(FolderPath, "volk.h")); + } } From 83a931de7e21f1dfdc79461f3fc5214b225019da Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 29 Oct 2023 01:57:48 +0200 Subject: [PATCH 68/78] Fix typo bug in DOF --- Source/Engine/Renderer/DepthOfFieldPass.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Renderer/DepthOfFieldPass.cpp b/Source/Engine/Renderer/DepthOfFieldPass.cpp index cd7930f5c..707a441e6 100644 --- a/Source/Engine/Renderer/DepthOfFieldPass.cpp +++ b/Source/Engine/Renderer/DepthOfFieldPass.cpp @@ -204,7 +204,7 @@ void DepthOfFieldPass::Render(RenderContext& renderContext, GPUTexture*& frame, { DepthOfFieldSettings& dofSettings = renderContext.List->Settings.DepthOfField; const bool useDoF = EnumHasAnyFlags(renderContext.View.Flags, ViewFlags::DepthOfField) && dofSettings.Enabled; - if (!useDoF || _platformSupportsDoF || checkIfSkipPass()) + if (!useDoF || !_platformSupportsDoF || checkIfSkipPass()) return; auto device = GPUDevice::Instance; auto context = device->GetMainContext(); From 56c9429e253960e2dbb16b80462877d812dc1662 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 29 Oct 2023 02:57:59 +0200 Subject: [PATCH 69/78] Fix spawning prefab without transform provided #1831 --- Source/Engine/Level/Prefabs/PrefabManager.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Source/Engine/Level/Prefabs/PrefabManager.cpp b/Source/Engine/Level/Prefabs/PrefabManager.cpp index cd9d893ec..de164343b 100644 --- a/Source/Engine/Level/Prefabs/PrefabManager.cpp +++ b/Source/Engine/Level/Prefabs/PrefabManager.cpp @@ -39,7 +39,7 @@ PrefabManagerService PrefabManagerServiceInstance; Actor* PrefabManager::SpawnPrefab(Prefab* prefab) { Actor* parent = Level::Scenes.Count() != 0 ? Level::Scenes.Get()[0] : nullptr; - return SpawnPrefab(prefab, Transform::Identity, parent, nullptr); + return SpawnPrefab(prefab, Transform(Vector3::Minimum), parent, nullptr); } Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Vector3& position) @@ -73,12 +73,12 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, Actor* parent, const Transform Actor* PrefabManager::SpawnPrefab(Prefab* prefab, Actor* parent) { - return SpawnPrefab(prefab, Transform::Identity, parent, nullptr); + return SpawnPrefab(prefab, Transform(Vector3::Minimum), parent, nullptr); } Actor* PrefabManager::SpawnPrefab(Prefab* prefab, Actor* parent, Dictionary* objectsCache, bool withSynchronization) { - return SpawnPrefab(prefab, Transform::Identity, parent, objectsCache, withSynchronization); + return SpawnPrefab(prefab, Transform(Vector3::Minimum), parent, objectsCache, withSynchronization); } Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Transform& transform, Actor* parent, Dictionary* objectsCache, bool withSynchronization) @@ -191,7 +191,7 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Transform& transform, Ac parent->Children.Add(root); // Move root to the right location - if (transform != Transform::Identity) + if (transform.Translation != Vector3::Minimum) root->SetTransform(transform); // Link actors hierarchy From f434ff2efe76ecaadbbbb7c075dd561febf2fd2a Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 29 Oct 2023 13:37:05 +0100 Subject: [PATCH 70/78] Fix memory overcommitment by `HashSet` when adding and removing the same item in a loop #1829 --- Source/Engine/Core/Collections/HashSet.h | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/Source/Engine/Core/Collections/HashSet.h b/Source/Engine/Core/Collections/HashSet.h index 107e42e65..fba8ca823 100644 --- a/Source/Engine/Core/Collections/HashSet.h +++ b/Source/Engine/Core/Collections/HashSet.h @@ -82,7 +82,6 @@ public: private: int32 _elementsCount = 0; - int32 _deletedCount = 0; int32 _size = 0; AllocationData _allocation; @@ -109,14 +108,11 @@ public: /// The other collection to move. HashSet(HashSet&& other) noexcept : _elementsCount(other._elementsCount) - , _deletedCount(other._deletedCount) , _size(other._size) { _elementsCount = other._elementsCount; - _deletedCount = other._deletedCount; _size = other._size; other._elementsCount = 0; - other._deletedCount = 0; other._size = 0; _allocation.Swap(other._allocation); } @@ -154,10 +150,8 @@ public: Clear(); _allocation.Free(); _elementsCount = other._elementsCount; - _deletedCount = other._deletedCount; _size = other._size; other._elementsCount = 0; - other._deletedCount = 0; other._size = 0; _allocation.Swap(other._allocation); } @@ -337,12 +331,12 @@ public: /// void Clear() { - if (_elementsCount + _deletedCount != 0) + if (_elementsCount != 0) { Bucket* data = _allocation.Get(); for (int32 i = 0; i < _size; i++) data[i].Free(); - _elementsCount = _deletedCount = 0; + _elementsCount = 0; } } @@ -377,7 +371,7 @@ public: oldAllocation.Swap(_allocation); const int32 oldSize = _size; const int32 oldElementsCount = _elementsCount; - _deletedCount = _elementsCount = 0; + _elementsCount = 0; if (capacity != 0 && (capacity & (capacity - 1)) != 0) { // Align capacity value to the next power of two (http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2) @@ -439,7 +433,7 @@ public: bool Add(const ItemType& item) { // Ensure to have enough memory for the next item (in case of new element insertion) - EnsureCapacity(_elementsCount + _deletedCount + 1); + EnsureCapacity(_elementsCount + 1); // Find location of the item or place to insert it FindPositionResult pos; @@ -485,7 +479,6 @@ public: { _allocation.Get()[pos.ObjectIndex].Delete(); _elementsCount--; - _deletedCount++; return true; } return false; @@ -504,7 +497,6 @@ public: ASSERT(_allocation.Get()[i._index].IsOccupied()); _allocation.Get()[i._index].Delete(); _elementsCount--; - _deletedCount++; return true; } return false; From 1fc972d6acff80abfa3e700d659efcf3ff3cf16e Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 30 Oct 2023 16:45:57 +0100 Subject: [PATCH 71/78] Fix stall in GC collect on iOS with AOT --- Source/Engine/Engine/NativeInterop.Unmanaged.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Source/Engine/Engine/NativeInterop.Unmanaged.cs b/Source/Engine/Engine/NativeInterop.Unmanaged.cs index 0391974b6..5af1569b9 100644 --- a/Source/Engine/Engine/NativeInterop.Unmanaged.cs +++ b/Source/Engine/Engine/NativeInterop.Unmanaged.cs @@ -1181,7 +1181,10 @@ namespace FlaxEngine.Interop [UnmanagedCallersOnly] internal static void GCCollect(int generation, int mode, bool blocking, bool compacting) { + // TODO: fix stall on iOS with AOT +#if !USE_AOT GC.Collect(generation, (GCCollectionMode)mode, blocking, compacting); +#endif } [UnmanagedCallersOnly] From 2d1d8cc31063177481fd95335525f7bba0aa6f71 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 30 Oct 2023 18:34:20 +0100 Subject: [PATCH 72/78] Fix typo --- Source/Engine/Scripting/Object.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Scripting/Object.cs b/Source/Engine/Scripting/Object.cs index c64532c2b..9fc7d8039 100644 --- a/Source/Engine/Scripting/Object.cs +++ b/Source/Engine/Scripting/Object.cs @@ -320,7 +320,7 @@ namespace FlaxEngine internal static partial Object Internal_Create2(string typeName); [LibraryImport("FlaxEngine", EntryPoint = "ObjectInternal_ManagedInstanceCreated", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(Interop.StringMarshaller))] - internal static partial void Internal_ManagedInstanceCreated(Object managedInstance, IntPtr theKlass); + internal static partial void Internal_ManagedInstanceCreated(Object managedInstance, IntPtr typeClass); [LibraryImport("FlaxEngine", EntryPoint = "ObjectInternal_ManagedInstanceDeleted", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(Interop.StringMarshaller))] internal static partial void Internal_ManagedInstanceDeleted(IntPtr nativeInstance); From 1567c1c8d6c998e043f39c511e2acd562f7dbaa9 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 31 Oct 2023 14:15:35 +0100 Subject: [PATCH 73/78] Fix Mono AOT on iOS to not freeze during GC stop-the-world event on memory collection --- Source/Engine/Engine/NativeInterop.Unmanaged.cs | 3 --- Source/Engine/Scripting/Runtime/DotNet.cpp | 7 +++++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Source/Engine/Engine/NativeInterop.Unmanaged.cs b/Source/Engine/Engine/NativeInterop.Unmanaged.cs index 5af1569b9..0391974b6 100644 --- a/Source/Engine/Engine/NativeInterop.Unmanaged.cs +++ b/Source/Engine/Engine/NativeInterop.Unmanaged.cs @@ -1181,10 +1181,7 @@ namespace FlaxEngine.Interop [UnmanagedCallersOnly] internal static void GCCollect(int generation, int mode, bool blocking, bool compacting) { - // TODO: fix stall on iOS with AOT -#if !USE_AOT GC.Collect(generation, (GCCollectionMode)mode, blocking, compacting); -#endif } [UnmanagedCallersOnly] diff --git a/Source/Engine/Scripting/Runtime/DotNet.cpp b/Source/Engine/Scripting/Runtime/DotNet.cpp index b3e5a0072..91b1d3dde 100644 --- a/Source/Engine/Scripting/Runtime/DotNet.cpp +++ b/Source/Engine/Scripting/Runtime/DotNet.cpp @@ -46,6 +46,7 @@ #include #include #include +#include #include typedef char char_t; #define DOTNET_HOST_MONO_DEBUG 0 @@ -522,7 +523,8 @@ void MCore::GC::FreeMemory(void* ptr, bool coTaskMem) void MCore::Thread::Attach() { -#if DOTNET_HOST_MONO + // TODO: find a way to properly register native thread so Mono Stop The World (stw) won't freeze when native threads (eg. Job System) are running native code only +#if DOTNET_HOST_MONO && !USE_MONO_AOT 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(); From 4e2ee897bc8ebecc8bff5224cfd3705e23a376d9 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 31 Oct 2023 15:22:14 +0100 Subject: [PATCH 74/78] Fix missing codesign for macOS game binaries --- .../Flax.Build/Deploy/Deployment.Platforms.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Source/Tools/Flax.Build/Deploy/Deployment.Platforms.cs b/Source/Tools/Flax.Build/Deploy/Deployment.Platforms.cs index a2846aa14..e1d159ed5 100644 --- a/Source/Tools/Flax.Build/Deploy/Deployment.Platforms.cs +++ b/Source/Tools/Flax.Build/Deploy/Deployment.Platforms.cs @@ -55,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")); From f9614a48796c1b6e79375f5231062e82d874b440 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 31 Oct 2023 15:31:40 +0100 Subject: [PATCH 75/78] Disable capacity alloc on delegate creation --- Source/Engine/Core/Delegate.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Core/Delegate.h b/Source/Engine/Core/Delegate.h index 821d7916e..945dd6ce5 100644 --- a/Source/Engine/Core/Delegate.h +++ b/Source/Engine/Core/Delegate.h @@ -511,7 +511,7 @@ public: _locker = New(); ScopeLock lock(*_locker); if (_functions == nullptr) - _functions = New>(32); + _functions = New>(); _functions->Add(f); #endif } From b0fe99f1ec42cca00c9a54bdc6e96cbce84ce357 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 31 Oct 2023 16:11:09 +0100 Subject: [PATCH 76/78] Disable mono thread attach to fix current freeze on GC STW event --- Source/Engine/Scripting/Runtime/DotNet.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Scripting/Runtime/DotNet.cpp b/Source/Engine/Scripting/Runtime/DotNet.cpp index 91b1d3dde..47f9b0a7c 100644 --- a/Source/Engine/Scripting/Runtime/DotNet.cpp +++ b/Source/Engine/Scripting/Runtime/DotNet.cpp @@ -524,7 +524,7 @@ void MCore::GC::FreeMemory(void* ptr, bool coTaskMem) void MCore::Thread::Attach() { // 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 && !USE_MONO_AOT +#if DOTNET_HOST_MONO && 0 if (!IsInMainThread() && !mono_domain_get()) { mono_thread_attach(MonoDomainHandle); From 1a254afd4f610e80dfc8e21ec158d9a726263e35 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 1 Nov 2023 10:29:44 +0100 Subject: [PATCH 77/78] Fix crash when creating empty cloth --- Source/Engine/Physics/Actors/Cloth.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/Source/Engine/Physics/Actors/Cloth.cpp b/Source/Engine/Physics/Actors/Cloth.cpp index 7c180d98d..b2db80b0b 100644 --- a/Source/Engine/Physics/Actors/Cloth.cpp +++ b/Source/Engine/Physics/Actors/Cloth.cpp @@ -95,14 +95,17 @@ void Cloth::SetFabric(const FabricSettings& value) void Cloth::Rebuild() { #if WITH_CLOTH - // Remove old - if (IsDuringPlay()) - PhysicsBackend::RemoveCloth(GetPhysicsScene()->GetPhysicsScene(), _cloth); - DestroyCloth(); + if (_cloth) + { + // Remove old + if (IsDuringPlay()) + PhysicsBackend::RemoveCloth(GetPhysicsScene()->GetPhysicsScene(), _cloth); + DestroyCloth(); + } // Create new CreateCloth(); - if (IsDuringPlay()) + if (IsDuringPlay() && _cloth) PhysicsBackend::AddCloth(GetPhysicsScene()->GetPhysicsScene(), _cloth); #endif } From c0a8d29453d1388c5f71c7814e9a3d5af5d742a9 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 1 Nov 2023 10:46:47 +0100 Subject: [PATCH 78/78] Improve Cloth usage --- .../Dedicated/MeshReferenceEditor.cs | 17 +++++++++++++++-- Source/Editor/Tools/ClothPainting.cs | 5 +++-- Source/Engine/Physics/Actors/Cloth.h | 4 ++-- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/Source/Editor/CustomEditors/Dedicated/MeshReferenceEditor.cs b/Source/Editor/CustomEditors/Dedicated/MeshReferenceEditor.cs index 71c5f6daf..4099e5aee 100644 --- a/Source/Editor/CustomEditors/Dedicated/MeshReferenceEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/MeshReferenceEditor.cs @@ -225,8 +225,15 @@ namespace FlaxEditor.CustomEditors.Dedicated } _actor = actor; - var showActorPicker = actor == null || ParentEditor.Values.All(x => x is not Cloth); - if (showActorPicker) + if (ParentEditor.Values.Any(x => x is Cloth)) + { + // Cloth always picks the parent model mesh + if (actor == null) + { + layout.Label("Cloth needs to be added as a child to model actor."); + } + } + else { // Actor reference picker _actorPicker = layout.Custom(); @@ -242,7 +249,10 @@ namespace FlaxEditor.CustomEditors.Dedicated { var model = staticModel.Model; if (model == null || model.WaitForLoaded()) + { + layout.Label("No model."); return; + } var materials = model.MaterialSlots; var lods = model.LODs; meshNames = new string[lods.Length][]; @@ -267,7 +277,10 @@ namespace FlaxEditor.CustomEditors.Dedicated { var skinnedModel = animatedModel.SkinnedModel; if (skinnedModel == null || skinnedModel.WaitForLoaded()) + { + layout.Label("No model."); return; + } var materials = skinnedModel.MaterialSlots; var lods = skinnedModel.LODs; meshNames = new string[lods.Length][]; diff --git a/Source/Editor/Tools/ClothPainting.cs b/Source/Editor/Tools/ClothPainting.cs index 226c7b49c..f12fca9db 100644 --- a/Source/Editor/Tools/ClothPainting.cs +++ b/Source/Editor/Tools/ClothPainting.cs @@ -225,6 +225,7 @@ namespace FlaxEngine.Tools var cloth = _cloth; if (cloth == null) return; + var hasPaintInput = Owner.IsLeftMouseButtonDown && !Owner.IsAltKeyDown; // Perform detailed tracing to find cursor location for the brush var ray = Owner.MouseRay; @@ -240,7 +241,7 @@ namespace FlaxEngine.Tools // Cursor hit other object or nothing PaintEnd(); - if (Owner.IsLeftMouseButtonDown) + if (hasPaintInput) { // Select something else var view = new Ray(Owner.ViewPosition, Owner.ViewDirection); @@ -253,7 +254,7 @@ namespace FlaxEngine.Tools } // Handle painting - if (Owner.IsLeftMouseButtonDown) + if (hasPaintInput) PaintStart(); else PaintEnd(); diff --git a/Source/Engine/Physics/Actors/Cloth.h b/Source/Engine/Physics/Actors/Cloth.h index 4dc81ce3f..6137ec3b6 100644 --- a/Source/Engine/Physics/Actors/Cloth.h +++ b/Source/Engine/Physics/Actors/Cloth.h @@ -232,13 +232,13 @@ private: public: /// - /// Gets the mesh to use for the cloth simulation (single mesh from specific LOD). + /// Gets the mesh to use for the cloth simulation (single mesh from specific LOD). Always from the parent static or animated model actor. /// API_PROPERTY(Attributes="EditorOrder(0), EditorDisplay(\"Cloth\")") ModelInstanceActor::MeshReference GetMesh() const; /// - /// Sets the mesh to use for the cloth simulation (single mesh from specific LOD). + /// Sets the mesh to use for the cloth simulation (single mesh from specific LOD). Always from the parent static or animated model actor. /// API_PROPERTY() void SetMesh(const ModelInstanceActor::MeshReference& value);