From 2607e0e187d51a9da4ae56fce58b2a7ee83c435e Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Wed, 31 May 2023 10:38:33 -0500 Subject: [PATCH 01/76] Add ability to rotate colliders and add collider direction for capsule collider. --- .../Physics/Colliders/CapsuleCollider.cpp | 37 +++++++++++-- .../Physics/Colliders/CapsuleCollider.h | 27 ++++++++++ Source/Engine/Physics/Colliders/Collider.cpp | 54 +++++++++++++++---- Source/Engine/Physics/Colliders/Collider.h | 14 +++++ 4 files changed, 120 insertions(+), 12 deletions(-) diff --git a/Source/Engine/Physics/Colliders/CapsuleCollider.cpp b/Source/Engine/Physics/Colliders/CapsuleCollider.cpp index ba133e8b6..7728e6a3e 100644 --- a/Source/Engine/Physics/Colliders/CapsuleCollider.cpp +++ b/Source/Engine/Physics/Colliders/CapsuleCollider.cpp @@ -7,7 +7,9 @@ CapsuleCollider::CapsuleCollider(const SpawnParams& params) : Collider(params) , _radius(20.0f) , _height(100.0f) + , _direction(ColliderOrientationDirection::YAxis) { + SetDirection(_direction); } void CapsuleCollider::SetRadius(const float value) @@ -42,8 +44,10 @@ void CapsuleCollider::DrawPhysicsDebug(RenderView& view) const BoundingSphere sphere(_sphere.Center - view.Origin, _sphere.Radius); if (!view.CullingFrustum.Intersects(sphere)) return; + Quaternion collRot; + Quaternion::Multiply( _colliderOrientation, Quaternion::Euler(0, 90, 0), collRot); Quaternion rot; - Quaternion::Multiply(_transform.Orientation, Quaternion::Euler(0, 90, 0), rot); + Quaternion::Multiply(_transform.Orientation, collRot, rot); const float scaling = _cachedScale.GetAbsolute().MaxValue(); const float minSize = 0.001f; const float radius = Math::Max(Math::Abs(_radius) * scaling, minSize); @@ -54,10 +58,30 @@ void CapsuleCollider::DrawPhysicsDebug(RenderView& view) DEBUG_DRAW_WIRE_TUBE(_transform.LocalToWorld(_center), rot, radius, height, Color::GreenYellow * 0.8f, 0, true); } +void CapsuleCollider::SetDirection(ColliderOrientationDirection value) +{ + _direction = value; + switch (value) + { + case ColliderOrientationDirection::XAxis: + SetColliderOrientation(Quaternion::Identity); + break; + case ColliderOrientationDirection::YAxis: + SetColliderOrientation(Quaternion::Euler(0, 0, 90)); + break; + case ColliderOrientationDirection::ZAxis: + SetColliderOrientation(Quaternion::Euler(0, 90, 0)); + break; + default: ; + } +} + void CapsuleCollider::OnDebugDrawSelected() { + Quaternion collRot; + Quaternion::Multiply( _colliderOrientation, Quaternion::Euler(0, 90, 0), collRot); Quaternion rot; - Quaternion::Multiply(_transform.Orientation, Quaternion::Euler(0, 90, 0), rot); + Quaternion::Multiply(_transform.Orientation, collRot, rot); const float scaling = _cachedScale.GetAbsolute().MaxValue(); const float minSize = 0.001f; const float radius = Math::Max(Math::Abs(_radius) * scaling, minSize); @@ -84,6 +108,7 @@ void CapsuleCollider::Serialize(SerializeStream& stream, const void* otherObj) SERIALIZE_MEMBER(Radius, _radius); SERIALIZE_MEMBER(Height, _height); + SERIALIZE(_direction); } void CapsuleCollider::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) @@ -93,6 +118,8 @@ void CapsuleCollider::Deserialize(DeserializeStream& stream, ISerializeModifier* DESERIALIZE_MEMBER(Radius, _radius); DESERIALIZE_MEMBER(Height, _height); + DESERIALIZE(_direction); + SetDirection(_direction); } void CapsuleCollider::UpdateBounds() @@ -100,7 +127,11 @@ void CapsuleCollider::UpdateBounds() // Cache bounds const float radiusTwice = _radius * 2.0f; OrientedBoundingBox::CreateCentered(_center, Vector3(_height + radiusTwice, radiusTwice, radiusTwice), _orientedBox); - _orientedBox.Transform(_transform); + Transform transform = _transform; + Quaternion rot; + Quaternion::Multiply(transform.Orientation, _colliderOrientation, rot); + transform.Orientation = rot; + _orientedBox.Transform(transform); _orientedBox.GetBoundingBox(_box); BoundingSphere::FromBox(_box, _sphere); } diff --git a/Source/Engine/Physics/Colliders/CapsuleCollider.h b/Source/Engine/Physics/Colliders/CapsuleCollider.h index 5ae53cf53..210a49450 100644 --- a/Source/Engine/Physics/Colliders/CapsuleCollider.h +++ b/Source/Engine/Physics/Colliders/CapsuleCollider.h @@ -5,6 +5,16 @@ #include "Collider.h" #include "Engine/Core/Math/OrientedBoundingBox.h" +/// +/// The collider orientation direction. +/// +API_ENUM() enum class ColliderOrientationDirection +{ + XAxis, + YAxis, + ZAxis +}; + /// /// A capsule-shaped primitive collider. /// @@ -18,6 +28,7 @@ private: float _radius; float _height; OrientedBoundingBox _orientedBox; + ColliderOrientationDirection _direction; public: /// @@ -52,6 +63,22 @@ public: /// The capsule height will be scaled by the actor's world scale. API_PROPERTY() void SetHeight(float value); + /// + /// Gets the orientation direction of the capsule collider. + /// + /// The capsule height will be scaled by the actor's world scale. + API_PROPERTY(Attributes="EditorOrder(111), DefaultValue(typeof(ColliderOrientationDirection), \"YAxis\"), EditorDisplay(\"Collider\")") + FORCE_INLINE ColliderOrientationDirection GetDirection() const + { + return _direction; + } + + /// + /// Sets the orientation direction of the capsule collider. + /// + /// The capsule height will be scaled by the actor's world scale. + API_PROPERTY() void SetDirection(ColliderOrientationDirection value); + public: // [Collider] #if USE_EDITOR diff --git a/Source/Engine/Physics/Colliders/Collider.cpp b/Source/Engine/Physics/Colliders/Collider.cpp index 49de1f799..af8f85e9b 100644 --- a/Source/Engine/Physics/Colliders/Collider.cpp +++ b/Source/Engine/Physics/Colliders/Collider.cpp @@ -15,6 +15,7 @@ Collider::Collider(const SpawnParams& params) : PhysicsColliderActor(params) , _center(Float3::Zero) + , _colliderOrientation(Quaternion::Identity) , _isTrigger(false) , _shape(nullptr) , _staticActor(nullptr) @@ -45,11 +46,36 @@ void Collider::SetCenter(const Vector3& value) _center = value; if (_staticActor) { - PhysicsBackend::SetShapeLocalPose(_shape, _center, Quaternion::Identity); + Quaternion result; + Quaternion::Multiply(Quaternion::Identity, _colliderOrientation, result); + PhysicsBackend::SetShapeLocalPose(_shape, _center, result); } else if (const RigidBody* rigidBody = GetAttachedRigidBody()) { - PhysicsBackend::SetShapeLocalPose(_shape, (_localTransform.Translation + _localTransform.Orientation * _center) * rigidBody->GetScale(), _localTransform.Orientation); + Quaternion result; + Quaternion::Multiply(_localTransform.Orientation, _colliderOrientation, result); + PhysicsBackend::SetShapeLocalPose(_shape, (_localTransform.Translation + result * _center) * rigidBody->GetScale(), result); + } + UpdateBounds(); +} + +void Collider::SetColliderOrientation(const Quaternion& value) +{ + + if (Quaternion::NearEqual(value, _colliderOrientation)) + return; + _colliderOrientation = value; + if (_staticActor) + { + Quaternion result; + Quaternion::Multiply(Quaternion::Identity, _colliderOrientation, result); + PhysicsBackend::SetShapeLocalPose(_shape, _center, result); + } + else if (const RigidBody* rigidBody = GetAttachedRigidBody()) + { + Quaternion result; + Quaternion::Multiply(_localTransform.Orientation, _colliderOrientation, result); + PhysicsBackend::SetShapeLocalPose(_shape, (_localTransform.Translation + result * _center) * rigidBody->GetScale(), result); } UpdateBounds(); } @@ -169,8 +195,10 @@ void Collider::Attach(RigidBody* rigidBody) // Attach PhysicsBackend::AttachShape(_shape, rigidBody->GetPhysicsActor()); - _cachedLocalPosePos = (_localTransform.Translation + _localTransform.Orientation * _center) * rigidBody->GetScale(); - _cachedLocalPoseRot = _localTransform.Orientation; + Quaternion result; + Quaternion::Multiply(_localTransform.Orientation, _colliderOrientation, result); + _cachedLocalPosePos = (_localTransform.Translation + result * _center) * rigidBody->GetScale(); + _cachedLocalPoseRot = result; PhysicsBackend::SetShapeLocalPose(_shape, _cachedLocalPosePos, _cachedLocalPoseRot); if (rigidBody->IsDuringPlay()) { @@ -262,7 +290,9 @@ void Collider::CreateStaticActor() _staticActor = PhysicsBackend::CreateRigidStaticActor(nullptr, _transform.Translation, _transform.Orientation, scene); // Reset local pos of the shape and link it to the actor - PhysicsBackend::SetShapeLocalPose(_shape, _center, Quaternion::Identity); + Quaternion result; + Quaternion::Multiply(Quaternion::Identity, _colliderOrientation, result); + PhysicsBackend::SetShapeLocalPose(_shape, _center, result); PhysicsBackend::AttachShape(_shape, _staticActor); PhysicsBackend::AddSceneActor(scene, _staticActor); @@ -301,6 +331,7 @@ void Collider::Serialize(SerializeStream& stream, const void* otherObj) SERIALIZE_MEMBER(IsTrigger, _isTrigger); SERIALIZE_MEMBER(Center, _center); + SERIALIZE_MEMBER(ColliderOrientation, _colliderOrientation); SERIALIZE_MEMBER(ContactOffset, _contactOffset); SERIALIZE(Material); } @@ -312,6 +343,7 @@ void Collider::Deserialize(DeserializeStream& stream, ISerializeModifier* modifi DESERIALIZE_MEMBER(IsTrigger, _isTrigger); DESERIALIZE_MEMBER(Center, _center); + DESERIALIZE_MEMBER(ColliderOrientation, _colliderOrientation); DESERIALIZE_MEMBER(ContactOffset, _contactOffset); DESERIALIZE(Material); } @@ -429,15 +461,19 @@ void Collider::OnTransformChanged() if (_staticActor) { - PhysicsBackend::SetRigidActorPose(_staticActor, _transform.Translation, _transform.Orientation); + Quaternion result; + Quaternion::Multiply(_localTransform.Orientation, _colliderOrientation, result); + PhysicsBackend::SetRigidActorPose(_staticActor, _transform.Translation, result); } else if (const RigidBody* rigidBody = GetAttachedRigidBody()) { - const Vector3 localPosePos = (_localTransform.Translation + _localTransform.Orientation * _center) * rigidBody->GetScale(); - if (_cachedLocalPosePos != localPosePos || _cachedLocalPoseRot != _localTransform.Orientation) + Quaternion result; + Quaternion::Multiply(_localTransform.Orientation, _colliderOrientation, result); + const Vector3 localPosePos = (_localTransform.Translation + result * _center) * rigidBody->GetScale(); + if (_cachedLocalPosePos != localPosePos || _cachedLocalPoseRot != result) { _cachedLocalPosePos = localPosePos; - _cachedLocalPoseRot = _localTransform.Orientation; + _cachedLocalPoseRot = result; PhysicsBackend::SetShapeLocalPose(_shape, localPosePos, _cachedLocalPoseRot); } } diff --git a/Source/Engine/Physics/Colliders/Collider.h b/Source/Engine/Physics/Colliders/Collider.h index 6b9dfcb9d..5afc5ab27 100644 --- a/Source/Engine/Physics/Colliders/Collider.h +++ b/Source/Engine/Physics/Colliders/Collider.h @@ -20,6 +20,7 @@ API_CLASS(Abstract) class FLAXENGINE_API Collider : public PhysicsColliderActor DECLARE_SCENE_OBJECT_ABSTRACT(Collider); protected: Vector3 _center; + Quaternion _colliderOrientation; bool _isTrigger; void* _shape; void* _staticActor; @@ -88,6 +89,19 @@ public: /// API_PROPERTY() void SetContactOffset(float value); + /// + /// Gets the collider's orientation, measured in the object's local space. + /// + FORCE_INLINE Quaternion GetColliderOrientation() const + { + return _colliderOrientation; + } + + /// + /// Sets the orientation of the collider, measured in the object's local space. + /// + void SetColliderOrientation(const Quaternion& value); + /// /// The physical material used to define the collider physical properties. /// From c4c55bfb612e5c7f92ade575d290f8e89c618c14 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Wed, 31 May 2023 11:28:26 -0500 Subject: [PATCH 02/76] Fix compilation errors --- .../Physics/Colliders/CapsuleCollider.cpp | 40 +++++++++---------- .../Physics/Colliders/CapsuleCollider.h | 17 ++++++-- 2 files changed, 33 insertions(+), 24 deletions(-) diff --git a/Source/Engine/Physics/Colliders/CapsuleCollider.cpp b/Source/Engine/Physics/Colliders/CapsuleCollider.cpp index 7728e6a3e..19a44cc52 100644 --- a/Source/Engine/Physics/Colliders/CapsuleCollider.cpp +++ b/Source/Engine/Physics/Colliders/CapsuleCollider.cpp @@ -9,7 +9,7 @@ CapsuleCollider::CapsuleCollider(const SpawnParams& params) , _height(100.0f) , _direction(ColliderOrientationDirection::YAxis) { - SetDirection(_direction); + SetColliderDirection(_direction); } void CapsuleCollider::SetRadius(const float value) @@ -34,6 +34,24 @@ void CapsuleCollider::SetHeight(const float value) UpdateBounds(); } +void CapsuleCollider::SetColliderDirection(ColliderOrientationDirection value) +{ + _direction = value; + switch (value) + { + case ColliderOrientationDirection::XAxis: + SetColliderOrientation(Quaternion::Identity); + break; + case ColliderOrientationDirection::YAxis: + SetColliderOrientation(Quaternion::Euler(0, 0, 90)); + break; + case ColliderOrientationDirection::ZAxis: + SetColliderOrientation(Quaternion::Euler(0, 90, 0)); + break; + default: ; + } +} + #if USE_EDITOR #include "Engine/Debug/DebugDraw.h" @@ -58,24 +76,6 @@ void CapsuleCollider::DrawPhysicsDebug(RenderView& view) DEBUG_DRAW_WIRE_TUBE(_transform.LocalToWorld(_center), rot, radius, height, Color::GreenYellow * 0.8f, 0, true); } -void CapsuleCollider::SetDirection(ColliderOrientationDirection value) -{ - _direction = value; - switch (value) - { - case ColliderOrientationDirection::XAxis: - SetColliderOrientation(Quaternion::Identity); - break; - case ColliderOrientationDirection::YAxis: - SetColliderOrientation(Quaternion::Euler(0, 0, 90)); - break; - case ColliderOrientationDirection::ZAxis: - SetColliderOrientation(Quaternion::Euler(0, 90, 0)); - break; - default: ; - } -} - void CapsuleCollider::OnDebugDrawSelected() { Quaternion collRot; @@ -119,7 +119,7 @@ void CapsuleCollider::Deserialize(DeserializeStream& stream, ISerializeModifier* DESERIALIZE_MEMBER(Radius, _radius); DESERIALIZE_MEMBER(Height, _height); DESERIALIZE(_direction); - SetDirection(_direction); + SetColliderDirection(_direction); } void CapsuleCollider::UpdateBounds() diff --git a/Source/Engine/Physics/Colliders/CapsuleCollider.h b/Source/Engine/Physics/Colliders/CapsuleCollider.h index 210a49450..ef910df32 100644 --- a/Source/Engine/Physics/Colliders/CapsuleCollider.h +++ b/Source/Engine/Physics/Colliders/CapsuleCollider.h @@ -10,8 +10,19 @@ /// API_ENUM() enum class ColliderOrientationDirection { + /// + /// Orient to the X-Axis. + /// XAxis, + + /// + /// Orient to the Y-Axis. + /// YAxis, + + /// + /// Orient to the Z-Axis. + /// ZAxis }; @@ -66,9 +77,8 @@ public: /// /// Gets the orientation direction of the capsule collider. /// - /// The capsule height will be scaled by the actor's world scale. API_PROPERTY(Attributes="EditorOrder(111), DefaultValue(typeof(ColliderOrientationDirection), \"YAxis\"), EditorDisplay(\"Collider\")") - FORCE_INLINE ColliderOrientationDirection GetDirection() const + FORCE_INLINE ColliderOrientationDirection GetColliderDirection() const { return _direction; } @@ -76,8 +86,7 @@ public: /// /// Sets the orientation direction of the capsule collider. /// - /// The capsule height will be scaled by the actor's world scale. - API_PROPERTY() void SetDirection(ColliderOrientationDirection value); + API_PROPERTY() void SetColliderDirection(ColliderOrientationDirection value); public: // [Collider] From bcce52ca22ab80bd29b95f7d60dd69df6a68abb8 Mon Sep 17 00:00:00 2001 From: Menotdan <32620310+Menotdan@users.noreply.github.com> Date: Fri, 8 Dec 2023 17:38:58 -0500 Subject: [PATCH 03/76] Refactor CurveEditor and KeyframesEditor to use input options. --- Source/Editor/GUI/CurveEditor.cs | 44 +++++++++---------- .../GUI/Timeline/GUI/KeyframesEditor.cs | 42 ++++++++---------- 2 files changed, 39 insertions(+), 47 deletions(-) diff --git a/Source/Editor/GUI/CurveEditor.cs b/Source/Editor/GUI/CurveEditor.cs index 22deec120..b93aedaf9 100644 --- a/Source/Editor/GUI/CurveEditor.cs +++ b/Source/Editor/GUI/CurveEditor.cs @@ -6,6 +6,7 @@ using System.Globalization; using System.Linq; using FlaxEditor.CustomEditors; using FlaxEditor.GUI.ContextMenu; +using FlaxEditor.Options; using FlaxEngine; using FlaxEngine.GUI; @@ -926,34 +927,29 @@ namespace FlaxEditor.GUI if (base.OnKeyDown(key)) return true; - switch (key) + InputOptions options = Editor.Instance.Options.Options.Input; + if (options.SelectAll.Process(this)) + { + SelectAll(); + UpdateTangents(); + return true; + } + else if (options.Delete.Process(this)) { - case KeyboardKeys.Delete: RemoveKeyframes(); return true; - case KeyboardKeys.A: - if (Root.GetKey(KeyboardKeys.Control)) - { - SelectAll(); - UpdateTangents(); - return true; - } - break; - case KeyboardKeys.C: - if (Root.GetKey(KeyboardKeys.Control)) - { - CopyKeyframes(); - return true; - } - break; - case KeyboardKeys.V: - if (Root.GetKey(KeyboardKeys.Control)) - { - KeyframesEditorUtils.Paste(this); - return true; - } - break; } + else if (options.Copy.Process(this)) + { + CopyKeyframes(); + return true; + } + else if (options.Paste.Process(this)) + { + KeyframesEditorUtils.Paste(this); + return true; + } + return false; } diff --git a/Source/Editor/GUI/Timeline/GUI/KeyframesEditor.cs b/Source/Editor/GUI/Timeline/GUI/KeyframesEditor.cs index cb781229f..87bcb9d10 100644 --- a/Source/Editor/GUI/Timeline/GUI/KeyframesEditor.cs +++ b/Source/Editor/GUI/Timeline/GUI/KeyframesEditor.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Text; using FlaxEditor.CustomEditors; using FlaxEditor.GUI.ContextMenu; +using FlaxEditor.Options; using FlaxEditor.Scripting; using FlaxEngine; using FlaxEngine.GUI; @@ -1268,33 +1269,28 @@ namespace FlaxEditor.GUI if (base.OnKeyDown(key)) return true; - switch (key) + InputOptions options = Editor.Instance.Options.Options.Input; + if (options.SelectAll.Process(this)) + { + SelectAll(); + return true; + } + else if (options.Delete.Process(this)) { - case KeyboardKeys.Delete: RemoveKeyframes(); return true; - case KeyboardKeys.A: - if (Root.GetKey(KeyboardKeys.Control)) - { - SelectAll(); - return true; - } - break; - case KeyboardKeys.C: - if (Root.GetKey(KeyboardKeys.Control)) - { - CopyKeyframes(); - return true; - } - break; - case KeyboardKeys.V: - if (Root.GetKey(KeyboardKeys.Control)) - { - KeyframesEditorUtils.Paste(this); - return true; - } - break; } + else if (options.Copy.Process(this)) + { + CopyKeyframes(); + return true; + } + else if (options.Paste.Process(this)) + { + KeyframesEditorUtils.Paste(this); + return true; + } + return false; } From 13cc45c3d70f1bb4a0dd20b18ae73c8e20165b48 Mon Sep 17 00:00:00 2001 From: Menotdan <32620310+Menotdan@users.noreply.github.com> Date: Fri, 8 Dec 2023 20:16:07 -0500 Subject: [PATCH 04/76] Add the option to deselect all whereve there is a select all, Refactor much of the codebase to use keybinds from InputOptions. --- Source/Editor/Content/GUI/ContentView.cs | 25 +++++-- Source/Editor/GUI/CurveEditor.Contents.cs | 1 + Source/Editor/GUI/CurveEditor.cs | 27 +++++-- .../GUI/Timeline/GUI/KeyframesEditor.cs | 27 +++++-- Source/Editor/GUI/Tree/Tree.cs | 34 +++++++-- Source/Editor/Modules/SceneEditingModule.cs | 17 ++++- Source/Editor/Modules/UIModule.cs | 4 ++ Source/Editor/Options/InputOptions.cs | 4 ++ Source/Editor/Surface/VisjectSurface.cs | 26 +++++-- Source/Editor/Utilities/Utils.cs | 1 + Source/Editor/Windows/DebugLogWindow.cs | 4 +- .../Windows/Search/ContentSearchWindow.cs | 4 +- Source/Engine/UI/GUI/Common/TextBoxBase.cs | 70 +++++++++---------- 13 files changed, 179 insertions(+), 65 deletions(-) diff --git a/Source/Editor/Content/GUI/ContentView.cs b/Source/Editor/Content/GUI/ContentView.cs index 259be104b..1f208fbf9 100644 --- a/Source/Editor/Content/GUI/ContentView.cs +++ b/Source/Editor/Content/GUI/ContentView.cs @@ -190,6 +190,7 @@ namespace FlaxEditor.Content.GUI OnDelete?.Invoke(_selection); }), new InputActionsContainer.Binding(options => options.SelectAll, SelectAll), + new InputActionsContainer.Binding(options => options.DeselectAll, DeselectAll), new InputActionsContainer.Binding(options => options.Rename, () => { if (HasSelection && _selection[0].CanRename) @@ -394,10 +395,7 @@ namespace FlaxEditor.Content.GUI PerformLayout(); } - /// - /// Selects all the items. - /// - public void SelectAll() + private void BulkSelectUpdate(bool select = true) { // Lock layout var wasLayoutLocked = IsLayoutLocked; @@ -405,13 +403,30 @@ namespace FlaxEditor.Content.GUI // Select items _selection.Clear(); - _selection.AddRange(_items); + if (select) + _selection.AddRange(_items); // Unload and perform UI layout IsLayoutLocked = wasLayoutLocked; PerformLayout(); } + /// + /// Selects all the items. + /// + public void SelectAll() + { + BulkSelectUpdate(true); + } + + /// + /// Deselects all the items. + /// + public void DeselectAll() + { + BulkSelectUpdate(false); + } + /// /// Deselects the specified item. /// diff --git a/Source/Editor/GUI/CurveEditor.Contents.cs b/Source/Editor/GUI/CurveEditor.Contents.cs index 2d3c2d606..67c1e3450 100644 --- a/Source/Editor/GUI/CurveEditor.Contents.cs +++ b/Source/Editor/GUI/CurveEditor.Contents.cs @@ -484,6 +484,7 @@ namespace FlaxEditor.GUI cm.AddSeparator(); cm.AddButton("Edit all keyframes", () => _editor.EditAllKeyframes(this, location)); cm.AddButton("Select all keyframes", _editor.SelectAll); + cm.AddButton("Deselect all keyframes", _editor.DeselectAll); cm.AddButton("Copy all keyframes", () => { _editor.SelectAll(); diff --git a/Source/Editor/GUI/CurveEditor.cs b/Source/Editor/GUI/CurveEditor.cs index b93aedaf9..04e0fb61a 100644 --- a/Source/Editor/GUI/CurveEditor.cs +++ b/Source/Editor/GUI/CurveEditor.cs @@ -714,15 +714,28 @@ namespace FlaxEditor.GUI } } + private void BulkSelectUpdate(bool select = true) + { + for (int i = 0; i < _points.Count; i++) + { + _points[i].IsSelected = select; + } + } + /// /// Selects all keyframes. /// public void SelectAll() { - for (int i = 0; i < _points.Count; i++) - { - _points[i].IsSelected = true; - } + BulkSelectUpdate(true); + } + + /// + /// Deselects all keyframes. + /// + public void DeselectAll() + { + BulkSelectUpdate(false); } /// @@ -934,6 +947,12 @@ namespace FlaxEditor.GUI UpdateTangents(); return true; } + else if (options.DeselectAll.Process(this)) + { + DeselectAll(); + UpdateTangents(); + return true; + } else if (options.Delete.Process(this)) { RemoveKeyframes(); diff --git a/Source/Editor/GUI/Timeline/GUI/KeyframesEditor.cs b/Source/Editor/GUI/Timeline/GUI/KeyframesEditor.cs index 87bcb9d10..023a07e30 100644 --- a/Source/Editor/GUI/Timeline/GUI/KeyframesEditor.cs +++ b/Source/Editor/GUI/Timeline/GUI/KeyframesEditor.cs @@ -433,6 +433,7 @@ namespace FlaxEditor.GUI if (_editor.EnableKeyframesValueEdit) cm.AddButton("Edit all keyframes", () => _editor.EditAllKeyframes(this, location)); cm.AddButton("Select all keyframes", _editor.SelectAll).Enabled = _editor._points.Count > 0; + cm.AddButton("Deselect all keyframes", _editor.DeselectAll).Enabled = _editor._points.Count > 0; cm.AddButton("Copy all keyframes", () => { _editor.SelectAll(); @@ -1210,15 +1211,28 @@ namespace FlaxEditor.GUI } } + private void BulkSelectUpdate(bool select = true) + { + for (int i = 0; i < _points.Count; i++) + { + _points[i].IsSelected = select; + } + } + /// /// Selects all keyframes. /// public void SelectAll() { - for (int i = 0; i < _points.Count; i++) - { - _points[i].IsSelected = true; - } + BulkSelectUpdate(true); + } + + /// + /// Deselects all keyframes. + /// + public void DeselectAll() + { + BulkSelectUpdate(false); } /// @@ -1275,6 +1289,11 @@ namespace FlaxEditor.GUI SelectAll(); return true; } + else if (options.DeselectAll.Process(this)) + { + DeselectAll(); + return true; + } else if (options.Delete.Process(this)) { RemoveKeyframes(); diff --git a/Source/Editor/GUI/Tree/Tree.cs b/Source/Editor/GUI/Tree/Tree.cs index 3a9780ed9..9ebc8eb14 100644 --- a/Source/Editor/GUI/Tree/Tree.cs +++ b/Source/Editor/GUI/Tree/Tree.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Linq; +using FlaxEditor.Options; using FlaxEngine; using FlaxEngine.Assertions; using FlaxEngine.GUI; @@ -315,10 +316,7 @@ namespace FlaxEditor.GUI.Tree } } - /// - /// Select all expanded nodes - /// - public void SelectAllExpanded() + private void BulkSelectUpdateExpanded(bool select = true) { if (_supportMultiSelect) { @@ -327,7 +325,8 @@ namespace FlaxEditor.GUI.Tree // Update selection Selection.Clear(); - WalkSelectExpandedTree(Selection, _children[0] as TreeNode); + if (select) + WalkSelectExpandedTree(Selection, _children[0] as TreeNode); // Check if changed if (Selection.Count != prev.Count || !Selection.SequenceEqual(prev)) @@ -338,6 +337,22 @@ namespace FlaxEditor.GUI.Tree } } + /// + /// Select all expanded nodes + /// + public void SelectAllExpanded() + { + BulkSelectUpdateExpanded(true); + } + + /// + /// Deselect all nodes + /// + public void DeselectAll() + { + BulkSelectUpdateExpanded(false); + } + /// public override void Update(float deltaTime) { @@ -470,14 +485,19 @@ namespace FlaxEditor.GUI.Tree // Check if can use multi selection if (_supportMultiSelect) { - bool isCtrlDown = Root.GetKey(KeyboardKeys.Control); + InputOptions options = Editor.Instance.Options.Options.Input; // Select all expanded nodes - if (key == KeyboardKeys.A && isCtrlDown) + if (options.SelectAll.Process(this)) { SelectAllExpanded(); return true; } + else if (options.DeselectAll.Process(this)) + { + DeselectAll(); + return true; + } } return base.OnKeyDown(key); diff --git a/Source/Editor/Modules/SceneEditingModule.cs b/Source/Editor/Modules/SceneEditingModule.cs index 970ca8e99..b40e7bdc6 100644 --- a/Source/Editor/Modules/SceneEditingModule.cs +++ b/Source/Editor/Modules/SceneEditingModule.cs @@ -60,13 +60,26 @@ namespace FlaxEditor.Modules { } + private void BulkScenesSelectUpdate(bool select = true) + { + // Blank list deselects all + Select(select ? Editor.Scene.Root.ChildNodes : new List()); + } + /// /// Selects all scenes. /// public void SelectAllScenes() { - // Select all scenes (linked to the root node) - Select(Editor.Scene.Root.ChildNodes); + BulkScenesSelectUpdate(true); + } + + /// + /// Deselects all scenes. + /// + public void DeselectAllScenes() + { + BulkScenesSelectUpdate(false); } /// diff --git a/Source/Editor/Modules/UIModule.cs b/Source/Editor/Modules/UIModule.cs index 3386d411c..1a567ac91 100644 --- a/Source/Editor/Modules/UIModule.cs +++ b/Source/Editor/Modules/UIModule.cs @@ -53,6 +53,7 @@ namespace FlaxEditor.Modules private ContextMenuButton _menuEditDelete; private ContextMenuButton _menuEditDuplicate; private ContextMenuButton _menuEditSelectAll; + private ContextMenuButton _menuEditDeselectAll; private ContextMenuButton _menuEditFind; private ContextMenuButton _menuSceneMoveActorToViewport; private ContextMenuButton _menuSceneAlignActorWithViewport; @@ -553,6 +554,7 @@ namespace FlaxEditor.Modules _menuEditDuplicate = cm.AddButton("Duplicate", inputOptions.Duplicate, Editor.SceneEditing.Duplicate); cm.AddSeparator(); _menuEditSelectAll = cm.AddButton("Select all", inputOptions.SelectAll, Editor.SceneEditing.SelectAllScenes); + _menuEditDeselectAll = cm.AddButton("Deselect all", inputOptions.DeselectAll, Editor.SceneEditing.DeselectAllScenes); _menuEditFind = cm.AddButton("Find", inputOptions.Search, Editor.Windows.SceneWin.Search); cm.AddSeparator(); cm.AddButton("Game Settings", () => @@ -671,6 +673,7 @@ namespace FlaxEditor.Modules _menuEditDelete.ShortKeys = inputOptions.Delete.ToString(); _menuEditDuplicate.ShortKeys = inputOptions.Duplicate.ToString(); _menuEditSelectAll.ShortKeys = inputOptions.SelectAll.ToString(); + _menuEditDeselectAll.ShortKeys = inputOptions.DeselectAll.ToString(); _menuEditFind.ShortKeys = inputOptions.Search.ToString(); _menuGamePlayGame.ShortKeys = inputOptions.Play.ToString(); _menuGamePlayCurrentScenes.ShortKeys = inputOptions.PlayCurrentScenes.ToString(); @@ -861,6 +864,7 @@ namespace FlaxEditor.Modules _menuEditDelete.Enabled = hasSthSelected; _menuEditDuplicate.Enabled = hasSthSelected; _menuEditSelectAll.Enabled = Level.IsAnySceneLoaded; + _menuEditDeselectAll.Enabled = hasSthSelected; control.PerformLayout(); } diff --git a/Source/Editor/Options/InputOptions.cs b/Source/Editor/Options/InputOptions.cs index e2e7d0e71..55aef2753 100644 --- a/Source/Editor/Options/InputOptions.cs +++ b/Source/Editor/Options/InputOptions.cs @@ -56,6 +56,10 @@ namespace FlaxEditor.Options [EditorDisplay("Common"), EditorOrder(190)] public InputBinding SelectAll = new InputBinding(KeyboardKeys.A, KeyboardKeys.Control); + [DefaultValue(typeof(InputBinding), "Ctrl+Shift+A")] + [EditorDisplay("Common"), EditorOrder(195)] + public InputBinding DeselectAll = new InputBinding(KeyboardKeys.A, KeyboardKeys.Shift, KeyboardKeys.Control); + [DefaultValue(typeof(InputBinding), "F")] [EditorDisplay("Common"), EditorOrder(200)] public InputBinding FocusSelection = new InputBinding(KeyboardKeys.F); diff --git a/Source/Editor/Surface/VisjectSurface.cs b/Source/Editor/Surface/VisjectSurface.cs index 3ac702549..3086f197a 100644 --- a/Source/Editor/Surface/VisjectSurface.cs +++ b/Source/Editor/Surface/VisjectSurface.cs @@ -382,6 +382,7 @@ namespace FlaxEditor.Surface { new InputActionsContainer.Binding(options => options.Delete, Delete), new InputActionsContainer.Binding(options => options.SelectAll, SelectAll), + new InputActionsContainer.Binding(options => options.DeselectAll, DeselectAll), new InputActionsContainer.Binding(options => options.Copy, Copy), new InputActionsContainer.Binding(options => options.Paste, Paste), new InputActionsContainer.Binding(options => options.Cut, Cut), @@ -611,17 +612,14 @@ namespace FlaxEditor.Surface _context.MarkAsModified(graphEdited); } - /// - /// Selects all the nodes. - /// - public void SelectAll() + private void BulkSelectUpdate(bool select = true) { bool selectionChanged = false; for (int i = 0; i < _rootControl.Children.Count; i++) { - if (_rootControl.Children[i] is SurfaceControl control && !control.IsSelected) + if (_rootControl.Children[i] is SurfaceControl control && control.IsSelected != select) { - control.IsSelected = true; + control.IsSelected = select; selectionChanged = true; } } @@ -629,6 +627,22 @@ namespace FlaxEditor.Surface SelectionChanged?.Invoke(); } + /// + /// Selects all the nodes. + /// + public void SelectAll() + { + BulkSelectUpdate(true); + } + + /// + /// Deelects all the nodes. + /// + public void DeselectAll() + { + BulkSelectUpdate(false); + } + /// /// Clears the selection. /// diff --git a/Source/Editor/Utilities/Utils.cs b/Source/Editor/Utilities/Utils.cs index 7184391d5..63f3f9369 100644 --- a/Source/Editor/Utilities/Utils.cs +++ b/Source/Editor/Utilities/Utils.cs @@ -1289,6 +1289,7 @@ namespace FlaxEditor.Utilities inputActions.Add(options => options.Paste, Editor.Instance.SceneEditing.Paste); inputActions.Add(options => options.Duplicate, Editor.Instance.SceneEditing.Duplicate); inputActions.Add(options => options.SelectAll, Editor.Instance.SceneEditing.SelectAllScenes); + inputActions.Add(options => options.DeselectAll, Editor.Instance.SceneEditing.DeselectAllScenes); inputActions.Add(options => options.Delete, Editor.Instance.SceneEditing.Delete); inputActions.Add(options => options.Search, () => Editor.Instance.Windows.SceneWin.Search()); inputActions.Add(options => options.MoveActorToViewport, Editor.Instance.UI.MoveActorToViewport); diff --git a/Source/Editor/Windows/DebugLogWindow.cs b/Source/Editor/Windows/DebugLogWindow.cs index ab0c07ec5..14b0b85b0 100644 --- a/Source/Editor/Windows/DebugLogWindow.cs +++ b/Source/Editor/Windows/DebugLogWindow.cs @@ -170,6 +170,8 @@ namespace FlaxEditor.Windows /// public override bool OnKeyDown(KeyboardKeys key) { + InputOptions options = FlaxEditor.Editor.Instance.Options.Options.Input; + // Up if (key == KeyboardKeys.ArrowUp) { @@ -200,7 +202,7 @@ namespace FlaxEditor.Windows Open(); } // Ctrl+C - else if (key == KeyboardKeys.C && Root.GetKey(KeyboardKeys.Control)) + else if (options.Copy.Process(this)) { Copy(); return true; diff --git a/Source/Editor/Windows/Search/ContentSearchWindow.cs b/Source/Editor/Windows/Search/ContentSearchWindow.cs index 305fa16d6..49f8286df 100644 --- a/Source/Editor/Windows/Search/ContentSearchWindow.cs +++ b/Source/Editor/Windows/Search/ContentSearchWindow.cs @@ -12,6 +12,7 @@ using FlaxEditor.GUI.ContextMenu; using FlaxEditor.GUI.Docking; using FlaxEditor.GUI.Input; using FlaxEditor.GUI.Tree; +using FlaxEditor.Options; using FlaxEditor.Scripting; using FlaxEditor.Surface; using FlaxEditor.Windows; @@ -142,6 +143,7 @@ namespace FlaxEngine.Windows.Search /// public override bool OnKeyDown(KeyboardKeys key) { + InputOptions options = FlaxEditor.Editor.Instance.Options.Options.Input; if (IsFocused) { if (key == KeyboardKeys.Return && Navigate != null) @@ -149,7 +151,7 @@ namespace FlaxEngine.Windows.Search Navigate.Invoke(this); return true; } - if (key == KeyboardKeys.C && Root.GetKey(KeyboardKeys.Control)) + if (options.Copy.Process(this)) { Clipboard.Text = Text; return true; diff --git a/Source/Engine/UI/GUI/Common/TextBoxBase.cs b/Source/Engine/UI/GUI/Common/TextBoxBase.cs index 55b58634d..7feaea6b2 100644 --- a/Source/Engine/UI/GUI/Common/TextBoxBase.cs +++ b/Source/Engine/UI/GUI/Common/TextBoxBase.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; +using FlaxEditor.Options; using FlaxEngine.Assertions; using FlaxEngine.Utilities; @@ -1296,6 +1297,40 @@ namespace FlaxEngine.GUI bool ctrDown = window.GetKey(KeyboardKeys.Control); KeyDown?.Invoke(key); + // Handle controls that have bindings + InputOptions options = FlaxEditor.Editor.Instance.Options.Options.Input; + if (options.Copy.Process(this)) + { + Copy(); + return true; + } + else if (options.Paste.Process(this)) + { + Paste(); + return true; + } + else if (options.Duplicate.Process(this)) + { + Duplicate(); + return true; + } + else if (options.Cut.Process(this)) + { + Cut(); + return true; + } + else if (options.SelectAll.Process(this)) + { + SelectAll(); + return true; + } + else if (options.DeselectAll.Process(this)) + { + Deselect(); + return true; + } + + // Handle controls without bindings switch (key) { case KeyboardKeys.ArrowRight: @@ -1310,41 +1345,6 @@ namespace FlaxEngine.GUI case KeyboardKeys.ArrowDown: MoveDown(shiftDown, ctrDown); return true; - case KeyboardKeys.C: - if (ctrDown) - { - Copy(); - return true; - } - break; - case KeyboardKeys.V: - if (ctrDown) - { - Paste(); - return true; - } - break; - case KeyboardKeys.D: - if (ctrDown) - { - Duplicate(); - return true; - } - break; - case KeyboardKeys.X: - if (ctrDown) - { - Cut(); - return true; - } - break; - case KeyboardKeys.A: - if (ctrDown) - { - SelectAll(); - return true; - } - break; case KeyboardKeys.Backspace: { if (IsReadOnly) From 52d4dff5873f8bea6942b63f2ee521042b4c376e Mon Sep 17 00:00:00 2001 From: Menotdan <32620310+Menotdan@users.noreply.github.com> Date: Fri, 8 Dec 2023 22:11:54 -0500 Subject: [PATCH 05/76] Fix build regression involving TextBoxBase being used in both game builds and editor builds. --- Source/Engine/UI/GUI/Common/TextBoxBase.cs | 39 ++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/Source/Engine/UI/GUI/Common/TextBoxBase.cs b/Source/Engine/UI/GUI/Common/TextBoxBase.cs index 7feaea6b2..c1001e835 100644 --- a/Source/Engine/UI/GUI/Common/TextBoxBase.cs +++ b/Source/Engine/UI/GUI/Common/TextBoxBase.cs @@ -1,7 +1,9 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; +#if FLAX_EDITOR using FlaxEditor.Options; +#endif using FlaxEngine.Assertions; using FlaxEngine.Utilities; @@ -1298,6 +1300,7 @@ namespace FlaxEngine.GUI KeyDown?.Invoke(key); // Handle controls that have bindings +#if FLAX_EDITOR InputOptions options = FlaxEditor.Editor.Instance.Options.Options.Input; if (options.Copy.Process(this)) { @@ -1329,6 +1332,7 @@ namespace FlaxEngine.GUI Deselect(); return true; } +#endif // Handle controls without bindings switch (key) @@ -1345,6 +1349,41 @@ namespace FlaxEngine.GUI case KeyboardKeys.ArrowDown: MoveDown(shiftDown, ctrDown); return true; + case KeyboardKeys.C: + if (ctrDown) + { + Copy(); + return true; + } + break; + case KeyboardKeys.V: + if (ctrDown) + { + Paste(); + return true; + } + break; + case KeyboardKeys.D: + if (ctrDown) + { + Duplicate(); + return true; + } + break; + case KeyboardKeys.X: + if (ctrDown) + { + Cut(); + return true; + } + break; + case KeyboardKeys.A: + if (ctrDown) + { + SelectAll(); + return true; + } + break; case KeyboardKeys.Backspace: { if (IsReadOnly) From 87addf8197b3e5e03ffc3fe77d3fe9f751a006cf Mon Sep 17 00:00:00 2001 From: MineBill Date: Thu, 21 Dec 2023 19:49:48 +0200 Subject: [PATCH 06/76] Add the ability to quickly change window modes for the game window. --- Source/Editor/Modules/SimulationModule.cs | 7 +- Source/Editor/Modules/UIModule.cs | 18 +++++ Source/Editor/Options/InterfaceOptions.cs | 33 +++++++++ Source/Editor/Windows/GameWindow.cs | 87 +++++++++++++++++++++-- 4 files changed, 137 insertions(+), 8 deletions(-) diff --git a/Source/Editor/Modules/SimulationModule.cs b/Source/Editor/Modules/SimulationModule.cs index a5b2f13ba..13a3a8fab 100644 --- a/Source/Editor/Modules/SimulationModule.cs +++ b/Source/Editor/Modules/SimulationModule.cs @@ -264,11 +264,14 @@ namespace FlaxEditor.Modules _enterPlayFocusedWindow = gameWin; // Show Game widow if hidden - if (gameWin != null && gameWin.FocusOnPlay) + if (gameWin != null) { - gameWin.FocusGameViewport(); + if (gameWin.FocusOnPlay) + gameWin.FocusGameViewport(); + gameWin.SetWindowMode(Editor.Options.Options.Interface.DefaultGameWindowMode); } + Editor.Log("[PlayMode] Enter"); } diff --git a/Source/Editor/Modules/UIModule.cs b/Source/Editor/Modules/UIModule.cs index 3386d411c..a7735a37d 100644 --- a/Source/Editor/Modules/UIModule.cs +++ b/Source/Editor/Modules/UIModule.cs @@ -737,6 +737,17 @@ namespace FlaxEditor.Modules playActionGroup.SelectedChanged = SetPlayAction; Editor.Options.OptionsChanged += options => { playActionGroup.Selected = options.Interface.PlayButtonAction; }; + var windowModesGroup = new ContextMenuSingleSelectGroup(); + var windowTypeMenu = _toolStripPlay.ContextMenu.AddChildMenu("Game window mode"); + windowModesGroup.AddItem("Docked", InterfaceOptions.GameWindowMode.Docked, null, "Shows the game window docked, inside the editor"); + windowModesGroup.AddItem("Popup", InterfaceOptions.GameWindowMode.PopupWindow, null, "Shows the game window as a popup"); + windowModesGroup.AddItem("Maximized", InterfaceOptions.GameWindowMode.MaximizedWindow, null, "Shows the game window maximized (Same as pressing F11)"); + windowModesGroup.AddItem("Borderless", InterfaceOptions.GameWindowMode.BorderlessWindow, null, "Shows the game window borderless"); + windowModesGroup.AddItemsToContextMenu(windowTypeMenu.ContextMenu); + windowModesGroup.Selected = Editor.Options.Options.Interface.DefaultGameWindowMode; + windowModesGroup.SelectedChanged = SetGameWindowMode; + Editor.Options.OptionsChanged += options => { windowModesGroup.Selected = options.Interface.DefaultGameWindowMode; }; + _toolStripPause = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Pause64, Editor.Simulation.RequestResumeOrPause).LinkTooltip($"Pause/Resume game ({inputOptions.Pause})"); _toolStripStep = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Skip64, Editor.Simulation.RequestPlayOneFrame).LinkTooltip("Step one frame in game"); @@ -1033,6 +1044,13 @@ namespace FlaxEditor.Modules Editor.Options.Apply(options); } + private void SetGameWindowMode(InterfaceOptions.GameWindowMode newGameWindowMode) + { + var options = Editor.Options.Options; + options.Interface.DefaultGameWindowMode = newGameWindowMode; + Editor.Options.Apply(options); + } + private void OnMainWindowClosing() { // Clear UI references (GUI cannot be used after window closing) diff --git a/Source/Editor/Options/InterfaceOptions.cs b/Source/Editor/Options/InterfaceOptions.cs index 95a273f19..95cf84682 100644 --- a/Source/Editor/Options/InterfaceOptions.cs +++ b/Source/Editor/Options/InterfaceOptions.cs @@ -90,6 +90,32 @@ namespace FlaxEditor.Options PlayScenes, } + /// + /// Available window modes for the game window. + /// + public enum GameWindowMode + { + /// + /// Shows the game window docked, inside the editor. + /// + Docked, + + /// + /// Shows the game window as a popup. + /// + PopupWindow, + + /// + /// Shows the game window maximized. (Same as pressing F11) + /// + MaximizedWindow, + + /// + /// Shows the game window borderless. + /// + BorderlessWindow, + } + /// /// Gets or sets the Editor User Interface scale. Applied to all UI elements, windows and text. Can be used to scale the interface up on a bigger display. Editor restart required. /// @@ -229,6 +255,13 @@ namespace FlaxEditor.Options [EditorDisplay("Play In-Editor", "Play Button Action"), EditorOrder(410)] public PlayAction PlayButtonAction { get; set; } = PlayAction.PlayScenes; + /// + /// Gets or sets a value indicating how the game window should be displayed when the game is launched. + /// + [DefaultValue(GameWindowMode.Docked)] + [EditorDisplay("Play In-Editor", "Game Window Mode"), EditorOrder(420), Tooltip("Determines how the game window is displayed when the game is launched.")] + public GameWindowMode DefaultGameWindowMode { get; set; } = GameWindowMode.Docked; + /// /// Gets or sets a value indicating the number of game clients to launch when building and/or running cooked game. /// diff --git a/Source/Editor/Windows/GameWindow.cs b/Source/Editor/Windows/GameWindow.cs index 4a8c11176..1d6bf4022 100644 --- a/Source/Editor/Windows/GameWindow.cs +++ b/Source/Editor/Windows/GameWindow.cs @@ -23,6 +23,7 @@ namespace FlaxEditor.Windows private bool _showGUI = true; private bool _showDebugDraw = false; private bool _isMaximized = false, _isUnlockingMouse = false; + private bool _isFloating = false, _isBorderless = false; private bool _cursorVisible = true; private float _gameStartTime; private GUI.Docking.DockState _maximizeRestoreDockState; @@ -68,7 +69,7 @@ namespace FlaxEditor.Windows } /// - /// Gets or sets a value indicating whether game window is maximized (only in play mode). + /// Gets or sets a value indicating whether the game window is maximized (only in play mode). /// private bool IsMaximized { @@ -78,20 +79,42 @@ namespace FlaxEditor.Windows if (_isMaximized == value) return; _isMaximized = value; + if (value) + { + IsFloating = true; + var rootWindow = RootWindow; + rootWindow.Maximize(); + } + else + { + IsFloating = false; + } + } + } + + /// + /// Gets or sets a value indicating whether the game window is floating (popup, only in play mode). + /// + private bool IsFloating + { + get => _isFloating; + set + { + if (_isFloating == value) + return; + _isFloating = value; var rootWindow = RootWindow; if (value) { - // Maximize _maximizeRestoreDockTo = _dockedTo; _maximizeRestoreDockState = _dockedTo.TryGetDockState(out _); if (_maximizeRestoreDockState != GUI.Docking.DockState.Float) { var monitorBounds = Platform.GetMonitorBounds(PointToScreen(Size * 0.5f)); - ShowFloating(monitorBounds.Location + new Float2(200, 200), Float2.Zero, WindowStartPosition.Manual); - rootWindow = RootWindow; + var size = DefaultSize; + var location = monitorBounds.Location + monitorBounds.Size * 0.5f - size * 0.5f; + ShowFloating(location, size, WindowStartPosition.Manual); } - if (rootWindow != null && !rootWindow.IsMaximized) - rootWindow.Maximize(); } else { @@ -105,6 +128,33 @@ namespace FlaxEditor.Windows } } + /// + /// Gets or sets a value indicating whether the game window is borderless (only in play mode). + /// + private bool IsBorderless + { + get => _isBorderless; + set + { + if (_isBorderless == value) + return; + _isBorderless = value; + if (value) + { + IsFloating = true; + var rootWindow = RootWindow; + var monitorBounds = Platform.GetMonitorBounds(rootWindow.RootWindow.Window.ClientPosition); + rootWindow.Window.Position = monitorBounds.Location; + rootWindow.Window.SetBorderless(true); + rootWindow.Window.ClientSize = monitorBounds.Size; + } + else + { + IsFloating = false; + } + } + } + /// /// Gets or sets a value indicating whether center mouse position on window focus in play mode. Helps when working with games that lock mouse cursor. /// @@ -464,7 +514,9 @@ namespace FlaxEditor.Windows /// public override void OnPlayEnd() { + IsFloating = false; IsMaximized = false; + IsBorderless = false; Cursor = CursorType.Default; } @@ -930,6 +982,29 @@ namespace FlaxEditor.Windows Focus(); } + /// + /// Apply the selected window mode to the game window. + /// + /// + public void SetWindowMode(InterfaceOptions.GameWindowMode mode) + { + switch (mode) + { + case InterfaceOptions.GameWindowMode.Docked: + break; + case InterfaceOptions.GameWindowMode.PopupWindow: + IsFloating = true; + break; + case InterfaceOptions.GameWindowMode.MaximizedWindow: + IsMaximized = true; + break; + case InterfaceOptions.GameWindowMode.BorderlessWindow: + IsBorderless = true; + break; + default: throw new ArgumentOutOfRangeException(nameof(mode), mode, null); + } + } + /// /// Takes the screenshot of the current viewport. /// From 8ee2bf8d76b151bf3d467a5186584b8e760eb558 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Tue, 20 Feb 2024 20:57:15 -0600 Subject: [PATCH 07/76] Change category drop panel o look similar to other panels. Add flax engine scripts to flax engine category. --- Source/Editor/GUI/ItemsListContextMenu.cs | 13 +++++++++++-- Source/Engine/AI/Behavior.h | 2 +- .../Engine/Networking/Components/NetworkTransform.h | 2 +- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/Source/Editor/GUI/ItemsListContextMenu.cs b/Source/Editor/GUI/ItemsListContextMenu.cs index c8c2a9c22..df250a832 100644 --- a/Source/Editor/GUI/ItemsListContextMenu.cs +++ b/Source/Editor/GUI/ItemsListContextMenu.cs @@ -134,9 +134,13 @@ namespace FlaxEditor.GUI Render2D.FillRectangle(rect, color); } } + // Indent for drop panel items is handled by drop panel margin + var indent = Float2.Zero; + if (Parent is not DropPanel) + indent = new Float2(Editor.Instance.Icons.ArrowRight12.Size.X + 2, 0); // Draw name - Render2D.DrawText(style.FontSmall, Name, textRect, TintColor * (Enabled ? style.Foreground : style.ForegroundDisabled), TextAlignment.Near, TextAlignment.Center); + Render2D.DrawText(style.FontSmall, Name, textRect + indent, TintColor * (Enabled ? style.Foreground : style.ForegroundDisabled), TextAlignment.Near, TextAlignment.Center); } /// @@ -338,9 +342,14 @@ namespace FlaxEditor.GUI var categoryPanel = new DropPanel { HeaderText = item.Category, + ArrowImageOpened = new SpriteBrush(Editor.Instance.Icons.ArrowDown12), + ArrowImageClosed = new SpriteBrush(Editor.Instance.Icons.ArrowRight12), + EnableDropDownIcon = true, + ItemsMargin = new Margin(28, 0, 2, 2), + HeaderColor = Style.Current.Background, Parent = parent, }; - categoryPanel.Open(false); + categoryPanel.Close(false); _categoryPanels.Add(categoryPanel); parent = categoryPanel; } diff --git a/Source/Engine/AI/Behavior.h b/Source/Engine/AI/Behavior.h index 0bf69354d..c8381ca6d 100644 --- a/Source/Engine/AI/Behavior.h +++ b/Source/Engine/AI/Behavior.h @@ -11,7 +11,7 @@ /// /// Behavior instance script that runs Behavior Tree execution. /// -API_CLASS() class FLAXENGINE_API Behavior : public Script +API_CLASS(Attributes="Category(\"Flax Engine\")") class FLAXENGINE_API Behavior : public Script { API_AUTO_SERIALIZATION(); DECLARE_SCRIPTING_TYPE(Behavior); diff --git a/Source/Engine/Networking/Components/NetworkTransform.h b/Source/Engine/Networking/Components/NetworkTransform.h index 7a9b30ea6..ab358745f 100644 --- a/Source/Engine/Networking/Components/NetworkTransform.h +++ b/Source/Engine/Networking/Components/NetworkTransform.h @@ -10,7 +10,7 @@ /// Actor script component that synchronizes the Transform over the network. /// /// Interpolation and prediction logic based on https://www.gabrielgambetta.com/client-server-game-architecture.html. -API_CLASS(Namespace="FlaxEngine.Networking") class FLAXENGINE_API NetworkTransform : public Script, public INetworkSerializable +API_CLASS(Namespace="FlaxEngine.Networking", Attributes="Category(\"Flax Engine\")") class FLAXENGINE_API NetworkTransform : public Script, public INetworkSerializable { API_AUTO_SERIALIZATION(); DECLARE_SCRIPTING_TYPE(NetworkTransform); From 0a7662a37b51a0c641d3c7aeb4af7bdc809255f8 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Tue, 20 Feb 2024 21:13:28 -0600 Subject: [PATCH 08/76] Open drop panels while searching. --- Source/Editor/GUI/ItemsListContextMenu.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Source/Editor/GUI/ItemsListContextMenu.cs b/Source/Editor/GUI/ItemsListContextMenu.cs index df250a832..c52331b96 100644 --- a/Source/Editor/GUI/ItemsListContextMenu.cs +++ b/Source/Editor/GUI/ItemsListContextMenu.cs @@ -266,6 +266,10 @@ namespace FlaxEditor.GUI } } category.Visible = anyVisible; + if (string.IsNullOrEmpty(_searchBox.Text)) + category.Close(false); + else + category.Open(false); } } @@ -391,6 +395,7 @@ namespace FlaxEditor.GUI item2.UpdateFilter(null); } category.Visible = true; + category.Close(false); } } From 50e75e4e7b686fb31960fa605347a544b8646e75 Mon Sep 17 00:00:00 2001 From: Menotdan <32620310+Menotdan@users.noreply.github.com> Date: Thu, 22 Feb 2024 11:21:29 -0500 Subject: [PATCH 09/76] Fix IAssetFactory::Get() in plugins on Linux. --- Source/Engine/Content/Content.cpp | 2 ++ Source/Engine/Content/Factories/IAssetFactory.h | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Content/Content.cpp b/Source/Engine/Content/Content.cpp index 2be4df29d..b5fdded25 100644 --- a/Source/Engine/Content/Content.cpp +++ b/Source/Engine/Content/Content.cpp @@ -912,6 +912,8 @@ bool Content::IsAssetTypeIdInvalid(const ScriptingTypeHandle& type, const Script return true; } +Dictionary IAssetFactory::Factories; + Asset* Content::LoadAsync(const Guid& id, const ScriptingTypeHandle& type) { if (!id.IsValid()) diff --git a/Source/Engine/Content/Factories/IAssetFactory.h b/Source/Engine/Content/Factories/IAssetFactory.h index e7417c9dd..2aaa12bc5 100644 --- a/Source/Engine/Content/Factories/IAssetFactory.h +++ b/Source/Engine/Content/Factories/IAssetFactory.h @@ -16,13 +16,13 @@ class FLAXENGINE_API IAssetFactory { public: typedef Dictionary Collection; + static Collection Factories; /// /// Gets the all registered assets factories. Key is asset typename, value is the factory object. /// static Collection& Get() { - static Collection Factories(1024); return Factories; } From 2f87b87b45c1d3a02232a4fead397ee15a830c7f Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 26 Feb 2024 20:23:18 +0100 Subject: [PATCH 10/76] Minor fixes --- Source/Engine/Level/SceneObject.cpp | 4 +++- Source/Engine/Level/SceneObjectsFactory.cpp | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Level/SceneObject.cpp b/Source/Engine/Level/SceneObject.cpp index 713faf8ce..d286572e2 100644 --- a/Source/Engine/Level/SceneObject.cpp +++ b/Source/Engine/Level/SceneObject.cpp @@ -61,7 +61,7 @@ void SceneObject::BreakPrefabLink() String SceneObject::GetNamePath(Char separatorChar) const { - Array names; + Array names; const Actor* a = dynamic_cast(this); if (!a) a = GetParent(); @@ -75,6 +75,8 @@ String SceneObject::GetNamePath(Char separatorChar) const int32 length = names.Count() - 1; for (int32 i = 0; i < names.Count(); i++) length += names[i].Length(); + if (length == 0) + return String::Empty; String result; result.ReserveSpace(length); Char* ptr = result.Get(); diff --git a/Source/Engine/Level/SceneObjectsFactory.cpp b/Source/Engine/Level/SceneObjectsFactory.cpp index 54ef377f7..3df67d8ba 100644 --- a/Source/Engine/Level/SceneObjectsFactory.cpp +++ b/Source/Engine/Level/SceneObjectsFactory.cpp @@ -325,7 +325,7 @@ void SceneObjectsFactory::HandleObjectDeserializationError(const ISerializable:: dummyScript->Data = MoveTemp(bufferStr); } #endif - LOG(Warning, "Parent actor of the missing object: {0}", parent->GetName()); + LOG(Warning, "Parent actor of the missing object: '{0}' ({1})", parent->GetNamePath(), String(parent->GetType().Fullname)); } } #endif From 2d5d001a4491fbcbc870cfdaa3880b5da5a15aad Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 26 Feb 2024 20:34:59 +0100 Subject: [PATCH 11/76] Fix Missing Script replacing in prefab #1995 --- .../Dedicated/MissingScriptEditor.cs | 69 ++++++++++++------- 1 file changed, 44 insertions(+), 25 deletions(-) diff --git a/Source/Editor/CustomEditors/Dedicated/MissingScriptEditor.cs b/Source/Editor/CustomEditors/Dedicated/MissingScriptEditor.cs index bb18f6de0..81ca30361 100644 --- a/Source/Editor/CustomEditors/Dedicated/MissingScriptEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/MissingScriptEditor.cs @@ -67,25 +67,6 @@ public class MissingScriptEditor : GenericEditor base.Initialize(layout); } - private void FindActorsWithMatchingMissingScript(List missingScripts) - { - foreach (Actor actor in Level.GetActors(typeof(Actor))) - { - for (int scriptIndex = 0; scriptIndex < actor.ScriptsCount; scriptIndex++) - { - Script actorScript = actor.Scripts[scriptIndex]; - if (actorScript is not MissingScript missingActorScript) - continue; - - MissingScript currentMissing = Values[0] as MissingScript; - if (missingActorScript.MissingTypeName != currentMissing.MissingTypeName) - continue; - - missingScripts.Add(missingActorScript); - } - } - } - private void RunReplacementMultiCast(List actions) { if (actions.Count == 0) @@ -104,16 +85,54 @@ public class MissingScriptEditor : GenericEditor } } - private void ReplaceScript(ScriptType script, bool replaceAllInScene) + private void GetMissingScripts(Actor actor, string currentMissingTypeName, List missingScripts) { - var actions = new List(4); + // Iterate over the scripts of this actor + for (int i = 0; i < actor.ScriptsCount; i++) + { + if (actor.GetScript(i) is MissingScript otherMissing && + otherMissing.MissingTypeName == currentMissingTypeName) + { + missingScripts.Add(otherMissing); + } + } + // Iterate over this actor children (recursive) + for (int i = 0; i < actor.ChildrenCount; i++) + { + GetMissingScripts(actor.GetChild(i), currentMissingTypeName, missingScripts); + } + } + + private void ReplaceScript(ScriptType script) + { var missingScripts = new List(); - if (!replaceAllInScene) - missingScripts.Add((MissingScript)Values[0]); + var currentMissing = (MissingScript)Values[0]; + var currentMissingTypeName = currentMissing.MissingTypeName; + if (_shouldReplaceAllCheckbox.Checked) + { + if (currentMissing.Scene == null && currentMissing.Actor != null) + { + // Find all missing scripts in prefab instance + GetMissingScripts(currentMissing.Actor.GetPrefabRoot(), currentMissingTypeName, missingScripts); + } + else + { + // Find all missing scripts in all loaded levels that match this type + for (int i = 0; i < Level.ScenesCount; i++) + { + GetMissingScripts(Level.GetScene(i), currentMissingTypeName, missingScripts); + } + } + } else - FindActorsWithMatchingMissingScript(missingScripts); + { + // Use the current instance only + foreach (var value in Values) + missingScripts.Add((MissingScript)value); + } + var actions = new List(4); foreach (var missingScript in missingScripts) actions.Add(AddRemoveScript.Add(missingScript.Actor, script)); RunReplacementMultiCast(actions); @@ -149,7 +168,7 @@ public class MissingScriptEditor : GenericEditor var cm = new ItemsListContextMenu(180); for (int i = 0; i < scripts.Count; i++) cm.AddItem(new TypeSearchPopup.TypeItemView(scripts[i])); - cm.ItemClicked += item => ReplaceScript((ScriptType)item.Tag, _shouldReplaceAllCheckbox.Checked); + cm.ItemClicked += item => ReplaceScript((ScriptType)item.Tag); cm.SortItems(); cm.Show(_dropPanel, _replaceScriptButton.BottomLeft - new Float2((cm.Width - _replaceScriptButton.Width) / 2, 0)); } From 470c108ed9cea1194f0f122abc86e177bf3ed658 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 26 Feb 2024 21:58:31 +0100 Subject: [PATCH 12/76] Fix bug with saving Missing Script into file #1995 --- Source/Engine/Level/SceneQuery.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Source/Engine/Level/SceneQuery.cpp b/Source/Engine/Level/SceneQuery.cpp index cbf52e6a2..6a408154d 100644 --- a/Source/Engine/Level/SceneQuery.cpp +++ b/Source/Engine/Level/SceneQuery.cpp @@ -3,6 +3,7 @@ #include "SceneQuery.h" #include "Engine/Scripting/Script.h" #include "Engine/Profiler/Profiler.h" +#include "Scripts/MissingScript.h" Actor* SceneQuery::RaycastScene(const Ray& ray) { @@ -51,6 +52,15 @@ bool GetAllSerializableSceneObjectsQuery(Actor* actor, Array& obje return false; objects.Add(actor); objects.Add(reinterpret_cast(actor->Scripts.Get()), actor->Scripts.Count()); +#if USE_EDITOR + // Skip saving Missing Script instances + for (int32 i = 0; i < actor->Scripts.Count(); i++) + { + const int32 idx = objects.Count() - i - 1; + if (objects.Get()[idx]->GetTypeHandle() == MissingScript::TypeInitializer) + objects.RemoveAtKeepOrder(idx); + } +#endif return true; } From 69053823104619df0197367a55e17eafce752175 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 26 Feb 2024 22:13:50 +0100 Subject: [PATCH 13/76] Fix custom context menu popup showing as child (eg. prefab diff) --- .../Editor/GUI/ContextMenu/ContextMenuBase.cs | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs b/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs index f2feed095..511cda2e5 100644 --- a/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs +++ b/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs @@ -168,30 +168,30 @@ namespace FlaxEditor.GUI.ContextMenu bool isUp = false, isLeft = false; if (UseAutomaticDirectionFix) { + var parentMenu = parent as ContextMenu; if (monitorBounds.Bottom < rightBottomLocationSS.Y) { - // Direction: up isUp = true; locationSS.Y -= dpiSize.Y; - - // Offset to fix sub-menu location - if (parent is ContextMenu menu && menu._childCM != null) + if (parentMenu != null && parentMenu._childCM != null) locationSS.Y += 30.0f * dpiScale; } - if (monitorBounds.Right < rightBottomLocationSS.X || _parentCM?.Direction == ContextMenuDirection.LeftDown || _parentCM?.Direction == ContextMenuDirection.LeftUp) + if (parentMenu == null) { - // Direction: left - isLeft = true; - - if (IsSubMenu && _parentCM != null) - { - locationSS.X -= _parentCM.Width + dpiSize.X; - } - else + if (monitorBounds.Right < rightBottomLocationSS.X) { + isLeft = true; locationSS.X -= dpiSize.X; } } + else if (monitorBounds.Right < rightBottomLocationSS.X || _parentCM?.Direction == ContextMenuDirection.LeftDown || _parentCM?.Direction == ContextMenuDirection.LeftUp) + { + isLeft = true; + if (IsSubMenu && _parentCM != null) + locationSS.X -= _parentCM.Width + dpiSize.X; + else + locationSS.X -= dpiSize.X; + } } // Update direction flag From 7428ecfe63e5189efb2e25a8ad6c866ce0f1ba74 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 26 Feb 2024 22:29:43 +0100 Subject: [PATCH 14/76] Add skipping showing and applying changes to prefab root actor transform via Level #2125 --- .../Editor/CustomEditors/Dedicated/ActorEditor.cs | 14 ++++++++------ Source/Editor/Modules/PrefabsModule.cs | 9 ++++++++- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs b/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs index db77e24e7..3699b8254 100644 --- a/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs @@ -245,9 +245,7 @@ namespace FlaxEditor.CustomEditors.Dedicated { // Special case for new Script added to actor if (editor.Values[0] is Script script && !script.HasPrefabLink) - { return CreateDiffNode(editor); - } // Skip if no change detected var isRefEdited = editor.Values.IsReferenceValueModified; @@ -258,9 +256,16 @@ namespace FlaxEditor.CustomEditors.Dedicated if (editor.ChildrenEditors.Count == 0 || (isRefEdited && editor is CollectionEditor)) result = CreateDiffNode(editor); bool isScriptEditorWithRefValue = editor is ScriptsEditor && editor.Values.HasReferenceValue; + bool isActorEditorInLevel = editor is ActorEditor && editor.Values[0] is Actor actor && actor.IsPrefabRoot && actor.Scene != null; for (int i = 0; i < editor.ChildrenEditors.Count; i++) { - var child = ProcessDiff(editor.ChildrenEditors[i], !isScriptEditorWithRefValue); + var childEditor = editor.ChildrenEditors[i]; + + // Special case for root actor transformation (can be applied only in Prefab editor, not in Level) + if (isActorEditorInLevel && childEditor.Values.Info.Name is "LocalPosition" or "LocalOrientation" or "LocalScale") + continue; + + var child = ProcessDiff(childEditor, !isScriptEditorWithRefValue); if (child != null) { if (result == null) @@ -276,7 +281,6 @@ namespace FlaxEditor.CustomEditors.Dedicated { var prefabObjectScript = prefabObjectScripts[j]; bool isRemoved = true; - for (int i = 0; i < editor.ChildrenEditors.Count; i++) { if (editor.ChildrenEditors[i].Values is ScriptsEditor.ScriptsContainer container && container.PrefabObjectId == prefabObjectScript.PrefabObjectID) @@ -286,14 +290,12 @@ namespace FlaxEditor.CustomEditors.Dedicated break; } } - if (isRemoved) { var dummy = new RemovedScriptDummy { PrefabObject = prefabObjectScript }; - var child = CreateDiffNode(dummy); if (result == null) result = CreateDiffNode(editor); diff --git a/Source/Editor/Modules/PrefabsModule.cs b/Source/Editor/Modules/PrefabsModule.cs index 5cd212db0..e0e464c04 100644 --- a/Source/Editor/Modules/PrefabsModule.cs +++ b/Source/Editor/Modules/PrefabsModule.cs @@ -225,8 +225,15 @@ namespace FlaxEditor.Modules throw new ArgumentException("Missing prefab to apply."); PrefabApplying?.Invoke(prefab, instance); + // When applying changes to prefab from actor in level ignore it's root transformation (see ActorEditor.ProcessDiff) + var originalTransform = instance.LocalTransform; + if (instance.IsPrefabRoot && instance.Scene != null) + instance.LocalTransform = prefab.GetDefaultInstance().Transform; + // Call backend - if (PrefabManager.Internal_ApplyAll(FlaxEngine.Object.GetUnmanagedPtr(instance))) + var failed = PrefabManager.Internal_ApplyAll(FlaxEngine.Object.GetUnmanagedPtr(instance)); + instance.LocalTransform = originalTransform; + if (failed) throw new Exception("Failed to apply the prefab. See log to learn more."); PrefabApplied?.Invoke(prefab, instance); From f1b133bd6008b0c7e3acf4cf37e2458ec826fb33 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 27 Feb 2024 10:55:58 +0100 Subject: [PATCH 15/76] Add prefab link breaking to preserve nested prefabs links #1752 --- Source/Editor/Editor.cs | 3 + .../Editor/Managed/ManagedEditor.Internal.cpp | 15 +++ .../Undo/Actions/BreakPrefabLinkAction.cs | 111 ++++++++++-------- 3 files changed, 78 insertions(+), 51 deletions(-) diff --git a/Source/Editor/Editor.cs b/Source/Editor/Editor.cs index b37f0468c..4cee6d971 100644 --- a/Source/Editor/Editor.cs +++ b/Source/Editor/Editor.cs @@ -1676,6 +1676,9 @@ namespace FlaxEditor [return: MarshalAs(UnmanagedType.U1)] internal static partial bool Internal_CanSetToRoot(IntPtr prefab, IntPtr newRoot); + [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_GetPrefabNestedObject", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))] + internal static partial void Internal_GetPrefabNestedObject(IntPtr prefabId, IntPtr prefabObjectId, IntPtr outPrefabId, IntPtr outPrefabObjectId); + [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_GetAnimationTime", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))] internal static partial float Internal_GetAnimationTime(IntPtr animatedModel); diff --git a/Source/Editor/Managed/ManagedEditor.Internal.cpp b/Source/Editor/Managed/ManagedEditor.Internal.cpp index 9c6c73dfd..43c805bf4 100644 --- a/Source/Editor/Managed/ManagedEditor.Internal.cpp +++ b/Source/Editor/Managed/ManagedEditor.Internal.cpp @@ -509,6 +509,21 @@ DEFINE_INTERNAL_CALL(bool) EditorInternal_CanSetToRoot(Prefab* prefab, Actor* ta return true; } +DEFINE_INTERNAL_CALL(void) EditorInternal_GetPrefabNestedObject(Guid* prefabId, Guid* prefabObjectId, Guid* outPrefabId, Guid* outPrefabObjectId) +{ + *outPrefabId = Guid::Empty; + *outPrefabObjectId = Guid::Empty; + const auto prefab = Content::Load(*prefabId); + if (!prefab) + return; + const ISerializable::DeserializeStream** prefabObjectDataPtr = prefab->ObjectsDataCache.TryGet(*prefabObjectId); + if (!prefabObjectDataPtr) + return; + const ISerializable::DeserializeStream& prefabObjectData = **prefabObjectDataPtr; + JsonTools::GetGuidIfValid(*outPrefabId, prefabObjectData, "PrefabID"); + JsonTools::GetGuidIfValid(*outPrefabObjectId, prefabObjectData, "PrefabObjectID"); +} + DEFINE_INTERNAL_CALL(float) EditorInternal_GetAnimationTime(AnimatedModel* animatedModel) { return animatedModel && animatedModel->GraphInstance.State.Count() == 1 ? animatedModel->GraphInstance.State[0].Animation.TimePosition : 0.0f; diff --git a/Source/Editor/Undo/Actions/BreakPrefabLinkAction.cs b/Source/Editor/Undo/Actions/BreakPrefabLinkAction.cs index 08efc28d0..55553de25 100644 --- a/Source/Editor/Undo/Actions/BreakPrefabLinkAction.cs +++ b/Source/Editor/Undo/Actions/BreakPrefabLinkAction.cs @@ -17,6 +17,30 @@ namespace FlaxEditor.Actions [Serializable] sealed class BreakPrefabLinkAction : IUndoAction { + private struct Item + { + public Guid ID; + public Guid PrefabID; + public Guid PrefabObjectID; + + public unsafe Item(SceneObject obj, List nestedPrefabLinks) + { + ID = obj.ID; + PrefabID = obj.PrefabID; + PrefabObjectID = obj.PrefabObjectID; + if (nestedPrefabLinks != null) + { + // Check if this object comes from another nested prefab (to break link only from the top-level prefab) + Item nested; + nested.ID = ID; + fixed (Item* i = &this) + Editor.Internal_GetPrefabNestedObject(new IntPtr(&i->PrefabID), new IntPtr(&i->PrefabObjectID), new IntPtr(&nested.PrefabID), new IntPtr(&nested.PrefabObjectID)); + if (nested.PrefabID != Guid.Empty && nested.PrefabObjectID != Guid.Empty) + nestedPrefabLinks.Add(nested); + } + } + } + [Serialize] private readonly bool _isBreak; @@ -24,25 +48,18 @@ namespace FlaxEditor.Actions private Guid _actorId; [Serialize] - private Guid _prefabId; + private List _items = new(); - [Serialize] - private Dictionary _prefabObjectIds; - - private BreakPrefabLinkAction(bool isBreak, Guid actorId, Guid prefabId) + private BreakPrefabLinkAction(bool isBreak, Guid actorId) { _isBreak = isBreak; _actorId = actorId; - _prefabId = prefabId; } private BreakPrefabLinkAction(bool isBreak, Actor actor) { _isBreak = isBreak; _actorId = actor.ID; - _prefabId = actor.PrefabID; - - _prefabObjectIds = new Dictionary(1024); CollectIds(actor); } @@ -55,7 +72,7 @@ namespace FlaxEditor.Actions { if (actor == null) throw new ArgumentNullException(nameof(actor)); - return new BreakPrefabLinkAction(true, actor.ID, Guid.Empty); + return new BreakPrefabLinkAction(true, actor.ID); } /// @@ -96,53 +113,45 @@ namespace FlaxEditor.Actions /// public void Dispose() { - _prefabObjectIds.Clear(); + _items.Clear(); } private void DoLink() { - if (_prefabObjectIds == null) - throw new Exception("Cannot link prefab. Missing objects Ids mapping."); - var actor = Object.Find(ref _actorId); if (actor == null) throw new Exception("Cannot link prefab. Missing actor."); - // Restore cached links - foreach (var e in _prefabObjectIds) - { - var objId = e.Key; - var prefabObjId = e.Value; - - var obj = Object.Find(ref objId); - if (obj is Actor) - { - Actor.Internal_LinkPrefab(Object.GetUnmanagedPtr(obj), ref _prefabId, ref prefabObjId); - } - else if (obj is Script) - { - Script.Internal_LinkPrefab(Object.GetUnmanagedPtr(obj), ref _prefabId, ref prefabObjId); - } - } - - Editor.Instance.Scene.MarkSceneEdited(actor.Scene); - Editor.Instance.Windows.PropertiesWin.Presenter.BuildLayout(); + Link(_items); + Refresh(actor); } - private void CollectIds(Actor actor) + private void Link(List items) { - _prefabObjectIds.Add(actor.ID, actor.PrefabObjectID); + for (int i = 0; i < items.Count; i++) + { + var item = items[i]; + var obj = Object.Find(ref item.ID); + if (obj != null) + SceneObject.Internal_LinkPrefab(Object.GetUnmanagedPtr(obj), ref item.PrefabID, ref item.PrefabObjectID); + } + } + + private void CollectIds(Actor actor, List nestedPrefabLinks = null) + { + _items.Add(new Item(actor, nestedPrefabLinks)); for (int i = 0; i < actor.ChildrenCount; i++) - { - CollectIds(actor.GetChild(i)); - } + CollectIds(actor.GetChild(i), nestedPrefabLinks); for (int i = 0; i < actor.ScriptsCount; i++) - { - var script = actor.GetScript(i); - _prefabObjectIds.Add(script.ID, script.PrefabObjectID); - } + _items.Add(new Item(actor.GetScript(i), nestedPrefabLinks)); + } + + private void Refresh(Actor actor) + { + Editor.Instance.Scene.MarkSceneEdited(actor.Scene); + Editor.Instance.Windows.PropertiesWin.Presenter.BuildLayout(); } private void DoBreak() @@ -153,18 +162,18 @@ namespace FlaxEditor.Actions if (!actor.HasPrefabLink) throw new Exception("Cannot break missing prefab link."); - if (_prefabObjectIds == null) - _prefabObjectIds = new Dictionary(1024); - else - _prefabObjectIds.Clear(); - CollectIds(actor); - - _prefabId = actor.PrefabID; + // Cache 'prev' state and extract any nested prefab instances to remain + _items.Clear(); + var nestedPrefabLinks = new List(); + CollectIds(actor, nestedPrefabLinks); + // Break prefab linkage actor.BreakPrefabLink(); - Editor.Instance.Scene.MarkSceneEdited(actor.Scene); - Editor.Instance.Windows.PropertiesWin.Presenter.BuildLayout(); + // Restore prefab link for nested instances + Link(nestedPrefabLinks); + + Refresh(actor); } } } From aabc9f51bb1a5e4412463ca88a7b7b46430a101a Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 27 Feb 2024 11:22:10 +0100 Subject: [PATCH 16/76] Improve #2270 by moving mehtod to cpp file --- Source/Engine/Content/Content.cpp | 8 ++++++-- Source/Engine/Content/Factories/IAssetFactory.h | 6 +----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Source/Engine/Content/Content.cpp b/Source/Engine/Content/Content.cpp index 27738af06..810c0bb3c 100644 --- a/Source/Engine/Content/Content.cpp +++ b/Source/Engine/Content/Content.cpp @@ -198,6 +198,12 @@ void ContentService::Dispose() Graphics::DisposeDevice(); } +IAssetFactory::Collection& IAssetFactory::Get() +{ + static Collection Factories(1024); + return Factories; +} + AssetsCache* Content::GetRegistry() { return &Cache; @@ -912,8 +918,6 @@ bool Content::IsAssetTypeIdInvalid(const ScriptingTypeHandle& type, const Script return true; } -Dictionary IAssetFactory::Factories; - Asset* Content::LoadAsync(const Guid& id, const ScriptingTypeHandle& type) { if (!id.IsValid()) diff --git a/Source/Engine/Content/Factories/IAssetFactory.h b/Source/Engine/Content/Factories/IAssetFactory.h index 64ae5c122..e544ceca2 100644 --- a/Source/Engine/Content/Factories/IAssetFactory.h +++ b/Source/Engine/Content/Factories/IAssetFactory.h @@ -16,15 +16,11 @@ class FLAXENGINE_API IAssetFactory { public: typedef Dictionary Collection; - static Collection Factories; /// /// Gets the all registered assets factories. Key is asset typename, value is the factory object. /// - static Collection& Get() - { - return Factories; - } + static Collection& Get(); public: /// From e68c7366071df0615d1d8f6f1fc70dc5e9f7a6ad Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 27 Feb 2024 17:15:49 +0100 Subject: [PATCH 17/76] Fix `Any` state transitions UpdateStateTransitions #2244 --- Source/Engine/Animations/Graph/AnimGraph.h | 1 + Source/Engine/Animations/Graph/AnimGroup.Animation.cpp | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Animations/Graph/AnimGraph.h b/Source/Engine/Animations/Graph/AnimGraph.h index 45b1e1a1f..ee84e0b71 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.h +++ b/Source/Engine/Animations/Graph/AnimGraph.h @@ -900,5 +900,6 @@ private: Variant SampleState(AnimGraphContext& context, const AnimGraphNode* state); void InitStateTransition(AnimGraphContext& context, AnimGraphInstanceData::StateMachineBucket& stateMachineBucket, AnimGraphStateTransition* transition = nullptr); AnimGraphStateTransition* UpdateStateTransitions(AnimGraphContext& context, const AnimGraphNode::StateMachineData& stateMachineData, AnimGraphNode* state, AnimGraphNode* ignoreState = nullptr); + AnimGraphStateTransition* UpdateStateTransitions(AnimGraphContext& context, const AnimGraphNode::StateMachineData& stateMachineData, const AnimGraphNode::StateBaseData& stateData, AnimGraphNode* state, AnimGraphNode* ignoreState = nullptr); void UpdateStateTransitions(AnimGraphContext& context, const AnimGraphNode::StateMachineData& stateMachineData, AnimGraphInstanceData::StateMachineBucket& stateMachineBucket, const AnimGraphNode::StateBaseData& stateData); }; diff --git a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp index ade1e2ad9..b09624d65 100644 --- a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp +++ b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp @@ -563,9 +563,13 @@ void AnimGraphExecutor::InitStateTransition(AnimGraphContext& context, AnimGraph } AnimGraphStateTransition* AnimGraphExecutor::UpdateStateTransitions(AnimGraphContext& context, const AnimGraphNode::StateMachineData& stateMachineData, AnimGraphNode* state, AnimGraphNode* ignoreState) +{ + return UpdateStateTransitions(context, stateMachineData, state->Data.State, state, ignoreState); +} + +AnimGraphStateTransition* AnimGraphExecutor::UpdateStateTransitions(AnimGraphContext& context, const AnimGraphNode::StateMachineData& stateMachineData, const AnimGraphNode::StateBaseData& stateData, AnimGraphNode* state, AnimGraphNode* ignoreState) { int32 transitionIndex = 0; - const AnimGraphNode::StateBaseData& stateData = state->Data.State; while (transitionIndex < ANIM_GRAPH_MAX_STATE_TRANSITIONS && stateData.Transitions[transitionIndex] != AnimGraphNode::StateData::InvalidTransitionIndex) { const uint16 idx = stateData.Transitions[transitionIndex]; @@ -640,7 +644,7 @@ AnimGraphStateTransition* AnimGraphExecutor::UpdateStateTransitions(AnimGraphCon void AnimGraphExecutor::UpdateStateTransitions(AnimGraphContext& context, const AnimGraphNode::StateMachineData& stateMachineData, AnimGraphInstanceData::StateMachineBucket& stateMachineBucket, const AnimGraphNode::StateBaseData& stateData) { - AnimGraphStateTransition* transition = UpdateStateTransitions(context, stateMachineData, stateMachineBucket.CurrentState); + AnimGraphStateTransition* transition = UpdateStateTransitions(context, stateMachineData, stateData, stateMachineBucket.CurrentState); if (transition) { InitStateTransition(context, stateMachineBucket, transition); From 22a3f9565f05814294b316e175a86a5389739344 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 28 Feb 2024 13:26:24 +0100 Subject: [PATCH 18/76] Add old DrawRay api --- Source/Engine/Debug/DebugDraw.cpp | 5 +++++ Source/Engine/Debug/DebugDraw.h | 11 +++++++++++ 2 files changed, 16 insertions(+) diff --git a/Source/Engine/Debug/DebugDraw.cpp b/Source/Engine/Debug/DebugDraw.cpp index af71144a9..46b15a6e2 100644 --- a/Source/Engine/Debug/DebugDraw.cpp +++ b/Source/Engine/Debug/DebugDraw.cpp @@ -943,6 +943,11 @@ void DebugDraw::DrawDirection(const Vector3& origin, const Vector3& direction, c DrawLine(origin, origin + direction, color, duration, depthTest); } +void DebugDraw::DrawRay(const Vector3& origin, const Vector3& direction, const Color& color, float duration, bool depthTest) +{ + DrawLine(origin, origin + direction, color, duration, depthTest); +} + void DebugDraw::DrawRay(const Vector3& origin, const Vector3& direction, const Color& color, float length, float duration, bool depthTest) { if (isnan(length) || isinf(length)) diff --git a/Source/Engine/Debug/DebugDraw.h b/Source/Engine/Debug/DebugDraw.h index 03e90c369..30e4c905f 100644 --- a/Source/Engine/Debug/DebugDraw.h +++ b/Source/Engine/Debug/DebugDraw.h @@ -87,6 +87,17 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// If set to true depth test will be performed, otherwise depth will be ignored. API_FUNCTION() static void DrawDirection(const Vector3& origin, const Vector3& direction, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); + /// + /// Draws the line in a direction. + /// [Deprecated in v1.8] + /// + /// The origin of the line. + /// The direction of the line. + /// The color. + /// The duration (in seconds). Use 0 to draw it only once. + /// If set to true depth test will be performed, otherwise depth will be ignored. + API_FUNCTION() DEPRECATED static void DrawRay(const Vector3& origin, const Vector3& direction, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); + /// /// Draws the line in a direction. /// From 119a1e2b033edf081987e68d557b9fc5d0338529 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 28 Feb 2024 18:37:59 +0100 Subject: [PATCH 19/76] Add check to prevent incorrect `NavCrowd::RemoveAgent` usage --- Source/Engine/Navigation/NavCrowd.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/Engine/Navigation/NavCrowd.cpp b/Source/Engine/Navigation/NavCrowd.cpp index 330be870c..51f77da08 100644 --- a/Source/Engine/Navigation/NavCrowd.cpp +++ b/Source/Engine/Navigation/NavCrowd.cpp @@ -145,6 +145,7 @@ void NavCrowd::ResetAgentMove(int32 id) void NavCrowd::RemoveAgent(int32 id) { + CHECK(id >= 0 && id < _crowd->getAgentCount()); _crowd->removeAgent(id); } From dce0274a1c9c4e7c321bdb8b713c03b1bb3ab00f Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 28 Feb 2024 20:48:11 +0100 Subject: [PATCH 20/76] Fix regression in type search highlights from #2268 --- Source/Editor/GUI/ItemsListContextMenu.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Source/Editor/GUI/ItemsListContextMenu.cs b/Source/Editor/GUI/ItemsListContextMenu.cs index cf0ce83ac..78fbcd779 100644 --- a/Source/Editor/GUI/ItemsListContextMenu.cs +++ b/Source/Editor/GUI/ItemsListContextMenu.cs @@ -122,6 +122,10 @@ namespace FlaxEditor.GUI if (IsMouseOver || IsFocused) Render2D.FillRectangle(new Rectangle(Float2.Zero, Size), style.BackgroundHighlighted); + // Indent for drop panel items is handled by drop panel margin + if (Parent is not DropPanel) + textRect.Location += new Float2(Editor.Instance.Icons.ArrowRight12.Size.X + 2, 0); + // Draw all highlights if (_highlights != null) { @@ -134,13 +138,9 @@ namespace FlaxEditor.GUI Render2D.FillRectangle(rect, color); } } - // Indent for drop panel items is handled by drop panel margin - var indent = Float2.Zero; - if (Parent is not DropPanel) - indent = new Float2(Editor.Instance.Icons.ArrowRight12.Size.X + 2, 0); // Draw name - Render2D.DrawText(style.FontSmall, Name, textRect + indent, TintColor * (Enabled ? style.Foreground : style.ForegroundDisabled), TextAlignment.Near, TextAlignment.Center); + Render2D.DrawText(style.FontSmall, Name, textRect, TintColor * (Enabled ? style.Foreground : style.ForegroundDisabled), TextAlignment.Near, TextAlignment.Center); } /// From 508c07c5ea675893d1298480564d6018e4f78b17 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 28 Feb 2024 21:09:32 +0100 Subject: [PATCH 21/76] Add profile event for navcrowd init --- Source/Engine/Navigation/NavCrowd.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/Engine/Navigation/NavCrowd.cpp b/Source/Engine/Navigation/NavCrowd.cpp index 51f77da08..f933ea12f 100644 --- a/Source/Engine/Navigation/NavCrowd.cpp +++ b/Source/Engine/Navigation/NavCrowd.cpp @@ -46,6 +46,7 @@ bool NavCrowd::Init(float maxAgentRadius, int32 maxAgents, NavMeshRuntime* navMe { if (!_crowd || !navMesh) return true; + PROFILE_CPU(); // This can happen on game start when no navmesh is loaded yet (eg. navmesh tiles data is during streaming) so wait for navmesh if (navMesh->GetNavMesh() == nullptr) From 2873f1c4f9c1e29d178f376ec5c5b121feba39d6 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Fri, 1 Mar 2024 08:11:55 -0600 Subject: [PATCH 22/76] Add min and max values to limit the min and max that can be set for multiblend nodes. --- .../Surface/Archetypes/Animation.MultiBlend.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Source/Editor/Surface/Archetypes/Animation.MultiBlend.cs b/Source/Editor/Surface/Archetypes/Animation.MultiBlend.cs index 1a5e04e75..39eca356e 100644 --- a/Source/Editor/Surface/Archetypes/Animation.MultiBlend.cs +++ b/Source/Editor/Surface/Archetypes/Animation.MultiBlend.cs @@ -595,6 +595,11 @@ namespace FlaxEditor.Surface.Archetypes { var data0 = (Float4)_node.Values[0]; rangeX = new Float2(data0.X, data0.Y); + if (_node._animationX != null) + { + _node._animationX.MinValue = rangeX.X; + _node._animationX.MaxValue = rangeX.Y; + } rangeY = Float2.Zero; for (int i = 0; i < MaxAnimationsCount; i++) { @@ -725,7 +730,17 @@ namespace FlaxEditor.Surface.Archetypes { var data0 = (Float4)_node.Values[0]; rangeX = new Float2(data0.X, data0.Y); + if (_node._animationX != null) + { + _node._animationX.MinValue = rangeX.X; + _node._animationX.MaxValue = rangeX.Y; + } rangeY = new Float2(data0.Z, data0.W); + if (_node._animationY != null) + { + _node._animationY.MinValue = rangeY.X; + _node._animationY.MaxValue = rangeY.Y; + } for (int i = 0; i < MaxAnimationsCount; i++) { var dataA = (Float4)_node.Values[4 + i * 2]; From b25e9329724d34571472e7427326a2772c0afba0 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Fri, 1 Mar 2024 08:47:12 -0600 Subject: [PATCH 23/76] Move to ui update --- .../Archetypes/Animation.MultiBlend.cs | 23 +++++++------------ 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/Source/Editor/Surface/Archetypes/Animation.MultiBlend.cs b/Source/Editor/Surface/Archetypes/Animation.MultiBlend.cs index 39eca356e..8cb5eb986 100644 --- a/Source/Editor/Surface/Archetypes/Animation.MultiBlend.cs +++ b/Source/Editor/Surface/Archetypes/Animation.MultiBlend.cs @@ -595,11 +595,6 @@ namespace FlaxEditor.Surface.Archetypes { var data0 = (Float4)_node.Values[0]; rangeX = new Float2(data0.X, data0.Y); - if (_node._animationX != null) - { - _node._animationX.MinValue = rangeX.X; - _node._animationX.MaxValue = rangeX.Y; - } rangeY = Float2.Zero; for (int i = 0; i < MaxAnimationsCount; i++) { @@ -686,6 +681,9 @@ namespace FlaxEditor.Surface.Archetypes { _animationX.Value = 0.0f; } + var ranges = (Float4)Values[0]; + _animationX.MinValue = ranges.X; + _animationX.MaxValue = ranges.Y; _animationXLabel.Enabled = isValid; _animationX.Enabled = isValid; } @@ -730,17 +728,7 @@ namespace FlaxEditor.Surface.Archetypes { var data0 = (Float4)_node.Values[0]; rangeX = new Float2(data0.X, data0.Y); - if (_node._animationX != null) - { - _node._animationX.MinValue = rangeX.X; - _node._animationX.MaxValue = rangeX.Y; - } rangeY = new Float2(data0.Z, data0.W); - if (_node._animationY != null) - { - _node._animationY.MinValue = rangeY.X; - _node._animationY.MaxValue = rangeY.Y; - } for (int i = 0; i < MaxAnimationsCount; i++) { var dataA = (Float4)_node.Values[4 + i * 2]; @@ -858,6 +846,11 @@ namespace FlaxEditor.Surface.Archetypes _animationX.Value = 0.0f; _animationY.Value = 0.0f; } + var ranges = (Float4)Values[0]; + _animationX.MinValue = ranges.X; + _animationX.MaxValue = ranges.Y; + _animationY.MinValue = ranges.Z; + _animationY.MaxValue = ranges.W; _animationXLabel.Enabled = isValid; _animationX.Enabled = isValid; _animationYLabel.Enabled = isValid; From 2100f56eccb88da3b6f6486946d7514a07ac4035 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Fri, 1 Mar 2024 08:53:15 -0600 Subject: [PATCH 24/76] clamp points locations. --- Source/Editor/Surface/Archetypes/Animation.MultiBlend.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Editor/Surface/Archetypes/Animation.MultiBlend.cs b/Source/Editor/Surface/Archetypes/Animation.MultiBlend.cs index 8cb5eb986..2747e1023 100644 --- a/Source/Editor/Surface/Archetypes/Animation.MultiBlend.cs +++ b/Source/Editor/Surface/Archetypes/Animation.MultiBlend.cs @@ -602,7 +602,7 @@ namespace FlaxEditor.Surface.Archetypes var dataB = (Guid)_node.Values[5 + i * 2]; pointsAnims[i] = dataB; - pointsLocations[i] = new Float2(dataA.X, 0.0f); + pointsLocations[i] = new Float2(Mathf.Clamp(dataA.X, rangeX.X, rangeX.Y), 0.0f); } } @@ -735,7 +735,7 @@ namespace FlaxEditor.Surface.Archetypes var dataB = (Guid)_node.Values[5 + i * 2]; pointsAnims[i] = dataB; - pointsLocations[i] = new Float2(dataA.X, dataA.Y); + pointsLocations[i] = new Float2(Mathf.Clamp(dataA.X, rangeX.X, rangeX.Y), Mathf.Clamp(dataA.Y, rangeY.X, rangeY.Y)); } } From 23d1b3746d7d52f2b15f8265dc45ff0a7540b8af Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Fri, 1 Mar 2024 11:10:07 -0600 Subject: [PATCH 25/76] Remove ui postfx render task on canvas disable. --- Source/Engine/UI/UICanvas.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Source/Engine/UI/UICanvas.cs b/Source/Engine/UI/UICanvas.cs index e9663c07a..2d12dc12c 100644 --- a/Source/Engine/UI/UICanvas.cs +++ b/Source/Engine/UI/UICanvas.cs @@ -843,6 +843,13 @@ namespace FlaxEngine if (_renderer) { +#if FLAX_EDITOR + if (_editorTask != null) + { + _editorTask.RemoveCustomPostFx(_renderer); + return; + } +#endif SceneRenderTask.RemoveGlobalCustomPostFx(_renderer); } } From 1ff462461e23e00773322a60bb0ab01088755c47 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Fri, 1 Mar 2024 11:55:19 -0600 Subject: [PATCH 26/76] Re-add important inputs to GameWindow --- Source/Editor/Windows/GameWindow.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Source/Editor/Windows/GameWindow.cs b/Source/Editor/Windows/GameWindow.cs index f28adf0e6..0ab88924c 100644 --- a/Source/Editor/Windows/GameWindow.cs +++ b/Source/Editor/Windows/GameWindow.cs @@ -318,6 +318,9 @@ namespace FlaxEditor.Windows InputActions.Add(options => options.TakeScreenshot, () => Screenshot.Capture(string.Empty)); InputActions.Add(options => options.DebuggerUnlockMouse, UnlockMouseInPlay); InputActions.Add(options => options.ToggleFullscreen, () => { if (Editor.IsPlayMode) IsMaximized = !IsMaximized; }); + InputActions.Add(options => options.Play, Editor.Instance.Simulation.DelegatePlayOrStopPlayInEditor); + InputActions.Add(options => options.Pause, Editor.Instance.Simulation.RequestResumeOrPause); + InputActions.Add(options => options.StepFrame, Editor.Instance.Simulation.RequestPlayOneFrame); } private void ChangeViewportRatio(ViewportScaleOptions v) From 760ef3b8edcda78a5d0b1e766a483408a6bbc020 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sat, 2 Mar 2024 14:17:29 -0600 Subject: [PATCH 27/76] Add profiler inputs back to game window. --- Source/Editor/Windows/GameWindow.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Source/Editor/Windows/GameWindow.cs b/Source/Editor/Windows/GameWindow.cs index 0ab88924c..5ffdbe4a0 100644 --- a/Source/Editor/Windows/GameWindow.cs +++ b/Source/Editor/Windows/GameWindow.cs @@ -321,6 +321,17 @@ namespace FlaxEditor.Windows InputActions.Add(options => options.Play, Editor.Instance.Simulation.DelegatePlayOrStopPlayInEditor); InputActions.Add(options => options.Pause, Editor.Instance.Simulation.RequestResumeOrPause); InputActions.Add(options => options.StepFrame, Editor.Instance.Simulation.RequestPlayOneFrame); + InputActions.Add(options => options.ProfilerStartStop, () => + { + bool recording = !Editor.Instance.Windows.ProfilerWin.LiveRecording; + Editor.Instance.Windows.ProfilerWin.LiveRecording = recording; + Editor.Instance.UI.AddStatusMessage($"Profiling {(recording ? "started" : "stopped")}."); + }); + InputActions.Add(options => options.ProfilerClear, () => + { + Editor.Instance.Windows.ProfilerWin.Clear(); + Editor.Instance.UI.AddStatusMessage($"Profiling results cleared."); + }); } private void ChangeViewportRatio(ViewportScaleOptions v) From a19ae042f8ce093833df3d02763064f01d27d2a5 Mon Sep 17 00:00:00 2001 From: Nils Hausfeld Date: Sat, 2 Mar 2024 23:36:05 +0100 Subject: [PATCH 28/76] - Private parameters now get shown in VS CM --- Source/Editor/Surface/ContextMenu/VisjectCM.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/Source/Editor/Surface/ContextMenu/VisjectCM.cs b/Source/Editor/Surface/ContextMenu/VisjectCM.cs index 46d760673..96093001c 100644 --- a/Source/Editor/Surface/ContextMenu/VisjectCM.cs +++ b/Source/Editor/Surface/ContextMenu/VisjectCM.cs @@ -583,7 +583,7 @@ namespace FlaxEditor.Surface.ContextMenu // Check if surface has any parameters var parameters = _parametersGetter?.Invoke(); - int count = parameters?.Count(x => x.IsPublic) ?? 0; + int count = parameters?.Count ?? 0; if (count > 0) { // TODO: cache the allocated memory to reduce dynamic allocations @@ -596,8 +596,6 @@ namespace FlaxEditor.Surface.ContextMenu for (int i = 0; i < parameters.Count; i++) { var param = parameters[i]; - if (!param.IsPublic) - continue; var node = (NodeArchetype)_parameterGetNodeArchetype.Clone(); node.Title = "Get " + param.Name; @@ -629,10 +627,6 @@ namespace FlaxEditor.Surface.ContextMenu archetypeIndex = 0; for (int i = 0; i < parameters.Count; i++) { - var param = parameters[i]; - if (!param.IsPublic) - continue; - var item = new VisjectCMItem(group, groupArchetype, archetypes[archetypeIndex++]) { Parent = group From 5fbbf4ae728c7401ec77cf4a4dd81bf558482bb0 Mon Sep 17 00:00:00 2001 From: Nils Hausfeld Date: Sat, 2 Mar 2024 23:42:49 +0100 Subject: [PATCH 29/76] - Refactored UpdateSurfaceParametersGroup -- Removed 2nd for loop by merging both loops --- .../Editor/Surface/ContextMenu/VisjectCM.cs | 43 +++++++++---------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/Source/Editor/Surface/ContextMenu/VisjectCM.cs b/Source/Editor/Surface/ContextMenu/VisjectCM.cs index 96093001c..19ddcd294 100644 --- a/Source/Editor/Surface/ContextMenu/VisjectCM.cs +++ b/Source/Editor/Surface/ContextMenu/VisjectCM.cs @@ -592,26 +592,6 @@ namespace FlaxEditor.Surface.ContextMenu var archetypes = new NodeArchetype[count]; int archetypeIndex = 0; - // ReSharper disable once PossibleNullReferenceException - for (int i = 0; i < parameters.Count; i++) - { - var param = parameters[i]; - - var node = (NodeArchetype)_parameterGetNodeArchetype.Clone(); - node.Title = "Get " + param.Name; - node.DefaultValues[0] = param.ID; - archetypes[archetypeIndex++] = node; - - if (_parameterSetNodeArchetype != null) - { - node = (NodeArchetype)_parameterSetNodeArchetype.Clone(); - node.Title = "Set " + param.Name; - node.DefaultValues[0] = param.ID; - node.DefaultValues[1] = TypeUtils.GetDefaultValue(param.Type); - archetypes[archetypeIndex++] = node; - } - } - var groupArchetype = new GroupArchetype { GroupID = 6, @@ -624,22 +604,39 @@ namespace FlaxEditor.Surface.ContextMenu group.ArrowImageOpened = new SpriteBrush(Style.Current.ArrowDown); group.ArrowImageClosed = new SpriteBrush(Style.Current.ArrowRight); group.Close(false); - archetypeIndex = 0; + + // ReSharper disable once PossibleNullReferenceException for (int i = 0; i < parameters.Count; i++) { - var item = new VisjectCMItem(group, groupArchetype, archetypes[archetypeIndex++]) + var param = parameters[i]; + + // Define Getter node and create CM item + var node = (NodeArchetype)_parameterGetNodeArchetype.Clone(); + node.Title = "Get " + param.Name; + node.DefaultValues[0] = param.ID; + archetypes[archetypeIndex++] = node; + + var item = new VisjectCMItem(group, groupArchetype, node) { Parent = group }; + // Define Setter node and create CM item if parameter has a setter if (_parameterSetNodeArchetype != null) { - item = new VisjectCMItem(group, groupArchetype, archetypes[archetypeIndex++]) + node = (NodeArchetype)_parameterSetNodeArchetype.Clone(); + node.Title = "Set " + param.Name; + node.DefaultValues[0] = param.ID; + node.DefaultValues[1] = TypeUtils.GetDefaultValue(param.Type); + archetypes[archetypeIndex++] = node; + + item = new VisjectCMItem(group, groupArchetype, node) { Parent = group }; } } + group.SortChildren(); group.UnlockChildrenRecursive(); group.Parent = _groupsPanel; From b6879e76889818618e4c8919558c882162688121 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Sat, 2 Mar 2024 23:09:59 +0200 Subject: [PATCH 30/76] Fix concurrent access to cached marshalled type sizes Also optimize hot path for type size when we have generic type --- Source/Engine/Engine/NativeInterop.cs | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/Source/Engine/Engine/NativeInterop.cs b/Source/Engine/Engine/NativeInterop.cs index f17822e2c..9e871e2fb 100644 --- a/Source/Engine/Engine/NativeInterop.cs +++ b/Source/Engine/Engine/NativeInterop.cs @@ -48,7 +48,7 @@ namespace FlaxEngine.Interop #endif private static Dictionary classAttributesCacheCollectible = new(); private static Dictionary assemblyHandles = new(); - private static Dictionary _typeSizeCache = new(); + private static ConcurrentDictionary _typeSizeCache = new(); private static Dictionary loadedNativeLibraries = new(); internal static Dictionary libraryPaths = new(); @@ -1594,7 +1594,7 @@ namespace FlaxEngine.Interop private static IntPtr PinValue(T value) where T : struct { // Store the converted value in unmanaged memory so it will not be relocated by the garbage collector. - int size = GetTypeSize(typeof(T)); + int size = TypeHelpers.MarshalSize; uint index = Interlocked.Increment(ref pinnedAllocationsPointer) % (uint)pinnedAllocations.Length; ref (IntPtr ptr, int size) alloc = ref pinnedAllocations[index]; if (alloc.size < size) @@ -1727,25 +1727,36 @@ namespace FlaxEngine.Interop return tuple; } - internal static int GetTypeSize(Type type) + internal static class TypeHelpers { - if (!_typeSizeCache.TryGetValue(type, out var size)) + public static readonly int MarshalSize; + static TypeHelpers() { + Type type = typeof(T); try { var marshalType = type; if (type.IsEnum) marshalType = type.GetEnumUnderlyingType(); - size = Marshal.SizeOf(marshalType); + MarshalSize = Marshal.SizeOf(marshalType); } catch { // Workaround the issue where structure defined within generic type instance (eg. MyType.MyStruct) fails to get size // https://github.com/dotnet/runtime/issues/46426 - var obj = Activator.CreateInstance(type); - size = Marshal.SizeOf(obj); + var obj = RuntimeHelpers.GetUninitializedObject(type); + MarshalSize = Marshal.SizeOf(obj); } - _typeSizeCache.Add(type, size); + } + } + + internal static int GetTypeSize(Type type) + { + if (!_typeSizeCache.TryGetValue(type, out int size)) + { + var marshalSizeField = typeof(TypeHelpers<>).MakeGenericType(type).GetField(nameof(TypeHelpers.MarshalSize), BindingFlags.Static | BindingFlags.Public); + size = (int)marshalSizeField.GetValue(null); + _typeSizeCache.AddOrUpdate(type, size, (t, v) => size); } return size; } From 2175f46a10b260eed4f961a88a5d9dcf5cf0a74a Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 4 Mar 2024 14:23:17 +0100 Subject: [PATCH 31/76] Fix rpath on Linux to handle plugins libraries loading in Editor #1941 --- .../Flax.Build/Platforms/Linux/LinuxToolchain.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Source/Tools/Flax.Build/Platforms/Linux/LinuxToolchain.cs b/Source/Tools/Flax.Build/Platforms/Linux/LinuxToolchain.cs index b5ca869f8..6aa767fc3 100644 --- a/Source/Tools/Flax.Build/Platforms/Linux/LinuxToolchain.cs +++ b/Source/Tools/Flax.Build/Platforms/Linux/LinuxToolchain.cs @@ -99,6 +99,18 @@ namespace Flax.Build.Platforms //args.Add(string.Format("-Wl,-soname=\"{0}\"", soname)); } + // Include any external folders into rpath for proper dlopen (eg. when opening Editor project with plugins) + if (options.LinkEnv.Output == LinkerOutput.SharedLibrary || options.LinkEnv.Output == LinkerOutput.Executable) + { + var originDir = Path.GetDirectoryName(outputFilePath); + foreach (var lib in options.LinkEnv.InputLibraries) + { + var libDir = Path.GetDirectoryName(lib); + if (libDir != originDir) + args.Add($"-Wl,-rpath,\"{libDir}\""); + } + } + args.Add(string.Format("-target {0}", ArchitectureName)); args.Add(string.Format("--sysroot=\"{0}\"", ToolsetRoot.Replace('\\', '/'))); From 84d3103278ccd79b4da5839d8b39bd4ce061fafc Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 4 Mar 2024 14:26:47 +0100 Subject: [PATCH 32/76] Fix crash on incorrect object destruction event #1920 --- Source/Engine/Scripting/Scripting.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Source/Engine/Scripting/Scripting.cpp b/Source/Engine/Scripting/Scripting.cpp index fdde84c5a..31e400906 100644 --- a/Source/Engine/Scripting/Scripting.cpp +++ b/Source/Engine/Scripting/Scripting.cpp @@ -852,10 +852,12 @@ void ScriptingObjectReferenceBase::OnSet(ScriptingObject* object) void ScriptingObjectReferenceBase::OnDeleted(ScriptingObject* obj) { - ASSERT(_object == obj); - _object->Deleted.Unbind(this); - _object = nullptr; - Changed(); + if (_object == obj) + { + _object->Deleted.Unbind(this); + _object = nullptr; + Changed(); + } } ScriptingObject* Scripting::FindObject(Guid id, MClass* type) From 04761c69f14b84ae9c5d53f5cf2c866d398b0339 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 4 Mar 2024 14:43:48 +0100 Subject: [PATCH 33/76] Fix error loggig in headless mode on char16 platforms #2235 --- Source/Editor/Managed/ManagedEditor.cpp | 6 ------ Source/Engine/Platform/Base/PlatformBase.cpp | 6 ++++++ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Source/Editor/Managed/ManagedEditor.cpp b/Source/Editor/Managed/ManagedEditor.cpp index 0abe361ad..7a9e55b88 100644 --- a/Source/Editor/Managed/ManagedEditor.cpp +++ b/Source/Editor/Managed/ManagedEditor.cpp @@ -278,13 +278,7 @@ void ManagedEditor::Update() void ManagedEditor::Exit() { if (WasExitCalled) - { - // Ups xD - LOG(Warning, "Managed Editor exit called after exit or before init."); return; - } - - // Set flag WasExitCalled = true; // Skip if managed object is missing diff --git a/Source/Engine/Platform/Base/PlatformBase.cpp b/Source/Engine/Platform/Base/PlatformBase.cpp index 621d679f3..6adfd2c31 100644 --- a/Source/Engine/Platform/Base/PlatformBase.cpp +++ b/Source/Engine/Platform/Base/PlatformBase.cpp @@ -357,7 +357,13 @@ void PlatformBase::Error(const Char* msg) #if PLATFORM_HAS_HEADLESS_MODE if (CommandLine::Options.Headless) { +#if PLATFORM_TEXT_IS_CHAR16 + StringAnsi ansi(msg); + ansi += PLATFORM_LINE_TERMINATOR; + printf("Error: %s\n", ansi.Get()); +#else std::cout << "Error: " << msg << std::endl; +#endif } else #endif From 5fdf1789cefa5ee4b04fc63819eeb0df4602a545 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 4 Mar 2024 16:45:44 +0100 Subject: [PATCH 34/76] Fix support for utf8 character in path on unix systems #2187 --- Source/Engine/Main/Default/main.cpp | 4 +- .../Engine/Platform/Linux/LinuxFileSystem.cpp | 42 +++++++++---------- Source/Engine/Platform/Unix/UnixFile.cpp | 2 +- 3 files changed, 25 insertions(+), 23 deletions(-) diff --git a/Source/Engine/Main/Default/main.cpp b/Source/Engine/Main/Default/main.cpp index 6b192d0ae..9449eb008 100644 --- a/Source/Engine/Main/Default/main.cpp +++ b/Source/Engine/Main/Default/main.cpp @@ -11,7 +11,9 @@ int main(int argc, char* argv[]) StringBuilder args; for (int i = 1; i < argc; i++) { - args.Append(argv[i]); + String arg; + arg.SetUTF8(argv[i], StringUtils::Length(argv[i])); + args.Append(arg); if (i + 1 != argc) args.Append(TEXT(' ')); diff --git a/Source/Engine/Platform/Linux/LinuxFileSystem.cpp b/Source/Engine/Platform/Linux/LinuxFileSystem.cpp index 5995a9b31..26ee15a48 100644 --- a/Source/Engine/Platform/Linux/LinuxFileSystem.cpp +++ b/Source/Engine/Platform/Linux/LinuxFileSystem.cpp @@ -28,8 +28,8 @@ const DateTime UnixEpoch(1970, 1, 1); bool LinuxFileSystem::ShowOpenFileDialog(Window* parentWindow, const StringView& initialDirectory, const StringView& filter, bool multiSelect, const StringView& title, Array& filenames) { - const StringAsANSI<> initialDirectoryAnsi(*initialDirectory, initialDirectory.Length()); - const StringAsANSI<> titleAnsi(*title, title.Length()); + const StringAsUTF8<> initialDirectoryAnsi(*initialDirectory, initialDirectory.Length()); + const StringAsUTF8<> titleAnsi(*title, title.Length()); const char* initDir = initialDirectory.HasChars() ? initialDirectoryAnsi.Get() : "."; String xdgCurrentDesktop; StringBuilder fileFilter; @@ -113,7 +113,7 @@ bool LinuxFileSystem::ShowOpenFileDialog(Window* parentWindow, const StringView& bool LinuxFileSystem::ShowBrowseFolderDialog(Window* parentWindow, const StringView& initialDirectory, const StringView& title, String& path) { - const StringAsANSI<> titleAnsi(*title, title.Length()); + const StringAsUTF8<> titleAnsi(*title, title.Length()); String xdgCurrentDesktop; Platform::GetEnvironmentVariable(TEXT("XDG_CURRENT_DESKTOP"), xdgCurrentDesktop); @@ -158,7 +158,7 @@ bool LinuxFileSystem::ShowBrowseFolderDialog(Window* parentWindow, const StringV bool LinuxFileSystem::ShowFileExplorer(const StringView& path) { - const StringAsANSI<> pathAnsi(*path, path.Length()); + const StringAsUTF8<> pathAnsi(*path, path.Length()); char cmd[2048]; sprintf(cmd, "xdg-open %s &", pathAnsi.Get()); system(cmd); @@ -167,7 +167,7 @@ bool LinuxFileSystem::ShowFileExplorer(const StringView& path) bool LinuxFileSystem::CreateDirectory(const StringView& path) { - const StringAsANSI<> pathAnsi(*path, path.Length()); + const StringAsUTF8<> pathAnsi(*path, path.Length()); // Skip if already exists struct stat fileInfo; @@ -258,7 +258,7 @@ bool DeleteUnixPathTree(const char* path) bool LinuxFileSystem::DeleteDirectory(const String& path, bool deleteContents) { - const StringAsANSI<> pathANSI(*path, path.Length()); + const StringAsUTF8<> pathANSI(*path, path.Length()); if (deleteContents) { return DeleteUnixPathTree(pathANSI.Get()); @@ -272,7 +272,7 @@ bool LinuxFileSystem::DeleteDirectory(const String& path, bool deleteContents) bool LinuxFileSystem::DirectoryExists(const StringView& path) { struct stat fileInfo; - const StringAsANSI<> pathANSI(*path, path.Length()); + const StringAsUTF8<> pathANSI(*path, path.Length()); if (stat(pathANSI.Get(), &fileInfo) != -1) { return S_ISDIR(fileInfo.st_mode); @@ -282,8 +282,8 @@ bool LinuxFileSystem::DirectoryExists(const StringView& path) bool LinuxFileSystem::DirectoryGetFiles(Array& results, const String& path, const Char* searchPattern, DirectorySearchOption option) { - const StringAsANSI<> pathANSI(*path, path.Length()); - const StringAsANSI<> searchPatternANSI(searchPattern); + const StringAsUTF8<> pathANSI(*path, path.Length()); + const StringAsUTF8<> searchPatternANSI(searchPattern); // Check if use only top directory if (option == DirectorySearchOption::TopDirectoryOnly) @@ -297,7 +297,7 @@ bool LinuxFileSystem::GetChildDirectories(Array& results, const String& DIR* dir; struct stat statPath, statEntry; struct dirent* entry; - const StringAsANSI<> pathANSI(*directory, directory.Length()); + const StringAsUTF8<> pathANSI(*directory, directory.Length()); const char* path = pathANSI.Get(); // Stat for the path @@ -353,7 +353,7 @@ bool LinuxFileSystem::GetChildDirectories(Array& results, const String& bool LinuxFileSystem::FileExists(const StringView& path) { struct stat fileInfo; - const StringAsANSI<> pathANSI(*path, path.Length()); + const StringAsUTF8<> pathANSI(*path, path.Length()); if (stat(pathANSI.Get(), &fileInfo) != -1) { return S_ISREG(fileInfo.st_mode); @@ -363,7 +363,7 @@ bool LinuxFileSystem::FileExists(const StringView& path) bool LinuxFileSystem::DeleteFile(const StringView& path) { - const StringAsANSI<> pathANSI(*path, path.Length()); + const StringAsUTF8<> pathANSI(*path, path.Length()); return unlink(pathANSI.Get()) != 0; } @@ -371,7 +371,7 @@ uint64 LinuxFileSystem::GetFileSize(const StringView& path) { struct stat fileInfo; fileInfo.st_size = 0; - const StringAsANSI<> pathANSI(*path, path.Length()); + const StringAsUTF8<> pathANSI(*path, path.Length()); if (stat(pathANSI.Get(), &fileInfo) != -1) { // Check for directories @@ -385,7 +385,7 @@ uint64 LinuxFileSystem::GetFileSize(const StringView& path) bool LinuxFileSystem::IsReadOnly(const StringView& path) { - const StringAsANSI<> pathANSI(*path, path.Length()); + const StringAsUTF8<> pathANSI(*path, path.Length()); if (access(pathANSI.Get(), W_OK) == -1) { return errno == EACCES; @@ -395,7 +395,7 @@ bool LinuxFileSystem::IsReadOnly(const StringView& path) bool LinuxFileSystem::SetReadOnly(const StringView& path, bool isReadOnly) { - const StringAsANSI<> pathANSI(*path, path.Length()); + const StringAsUTF8<> pathANSI(*path, path.Length()); struct stat fileInfo; if (stat(pathANSI.Get(), &fileInfo) != -1) { @@ -422,15 +422,15 @@ bool LinuxFileSystem::MoveFile(const StringView& dst, const StringView& src, boo if (overwrite) { - unlink(StringAsANSI<>(*dst, dst.Length()).Get()); + unlink(StringAsUTF8<>(*dst, dst.Length()).Get()); } - if (rename(StringAsANSI<>(*src, src.Length()).Get(), StringAsANSI<>(*dst, dst.Length()).Get()) != 0) + if (rename(StringAsUTF8<>(*src, src.Length()).Get(), StringAsUTF8<>(*dst, dst.Length()).Get()) != 0) { if (errno == EXDEV) { if (!CopyFile(dst, src)) { - unlink(StringAsANSI<>(*src, src.Length()).Get()); + unlink(StringAsUTF8<>(*src, src.Length()).Get()); return false; } } @@ -441,8 +441,8 @@ bool LinuxFileSystem::MoveFile(const StringView& dst, const StringView& src, boo bool LinuxFileSystem::CopyFile(const StringView& dst, const StringView& src) { - const StringAsANSI<> srcANSI(*src, src.Length()); - const StringAsANSI<> dstANSI(*dst, dst.Length()); + const StringAsUTF8<> srcANSI(*src, src.Length()); + const StringAsUTF8<> dstANSI(*dst, dst.Length()); int srcFile, dstFile; char buffer[4096]; @@ -752,7 +752,7 @@ bool LinuxFileSystem::getFilesFromDirectoryAll(Array& results, const cha DateTime LinuxFileSystem::GetFileLastEditTime(const StringView& path) { struct stat fileInfo; - const StringAsANSI<> pathANSI(*path, path.Length()); + const StringAsUTF8<> pathANSI(*path, path.Length()); if (stat(pathANSI.Get(), &fileInfo) == -1) { return DateTime::MinValue(); diff --git a/Source/Engine/Platform/Unix/UnixFile.cpp b/Source/Engine/Platform/Unix/UnixFile.cpp index 7d94d01b6..741a0c002 100644 --- a/Source/Engine/Platform/Unix/UnixFile.cpp +++ b/Source/Engine/Platform/Unix/UnixFile.cpp @@ -73,7 +73,7 @@ UnixFile* UnixFile::Open(const StringView& path, FileMode mode, FileAccess acces if ((uint32)share & (uint32)FileShare::Delete) omode |= 0; - const StringAsANSI<> pathANSI(*path, path.Length()); + const StringAsUTF8<> pathANSI(*path, path.Length()); auto handle = open(pathANSI.Get(), flags, omode); if (handle == -1) { From bbe08be462ee66b8c55594d20360b0284eef66f4 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 4 Mar 2024 18:25:57 +0100 Subject: [PATCH 35/76] Fix duplicating array values in Editor #1959 --- Source/Editor/CustomEditors/CustomEditor.cs | 16 ++-------------- .../CustomEditors/Editors/ArrayEditor.cs | 4 ++-- Source/Editor/Utilities/Utils.cs | 18 +++++++++++++++++- 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/Source/Editor/CustomEditors/CustomEditor.cs b/Source/Editor/CustomEditors/CustomEditor.cs index ab87389de..ef6302e8e 100644 --- a/Source/Editor/CustomEditors/CustomEditor.cs +++ b/Source/Editor/CustomEditors/CustomEditor.cs @@ -747,7 +747,7 @@ namespace FlaxEditor.CustomEditors /// public void SetValueToDefault() { - SetValueCloned(Values.DefaultValue); + SetValue(Utilities.Utils.CloneValue(Values.DefaultValue)); } /// @@ -784,19 +784,7 @@ namespace FlaxEditor.CustomEditors return; } - SetValueCloned(Values.ReferenceValue); - } - - private void SetValueCloned(object value) - { - // For objects (eg. arrays) we need to clone them to prevent editing default/reference value within editor - if (value != null && !value.GetType().IsValueType) - { - var json = JsonSerializer.Serialize(value); - value = JsonSerializer.Deserialize(json, value.GetType()); - } - - SetValue(value); + SetValue(Utilities.Utils.CloneValue(Values.ReferenceValue)); } /// diff --git a/Source/Editor/CustomEditors/Editors/ArrayEditor.cs b/Source/Editor/CustomEditors/Editors/ArrayEditor.cs index 276417f96..25163febd 100644 --- a/Source/Editor/CustomEditors/Editors/ArrayEditor.cs +++ b/Source/Editor/CustomEditors/Editors/ArrayEditor.cs @@ -2,7 +2,6 @@ using System; using System.Collections; -using FlaxEditor.Scripting; using FlaxEngine; using FlaxEngine.Utilities; @@ -47,8 +46,9 @@ namespace FlaxEditor.CustomEditors.Editors if (elementType.IsValueType || NotNullItems) { // Fill new entries with the last value + var lastValue = array.GetValue(oldSize - 1); for (int i = oldSize; i < newSize; i++) - Array.Copy(array, oldSize - 1, newValues, i, 1); + newValues.SetValue(Utilities.Utils.CloneValue(lastValue), i); } else { diff --git a/Source/Editor/Utilities/Utils.cs b/Source/Editor/Utilities/Utils.cs index 54518bf9e..7e2d9f628 100644 --- a/Source/Editor/Utilities/Utils.cs +++ b/Source/Editor/Utilities/Utils.cs @@ -11,7 +11,6 @@ using System.Globalization; using System.Collections.Generic; using System.IO; using System.Reflection; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; using System.Text.RegularExpressions; @@ -20,6 +19,7 @@ using FlaxEditor.GUI.Input; using FlaxEditor.GUI.Tree; using FlaxEditor.SceneGraph; using FlaxEngine; +using FlaxEngine.Json; using FlaxEngine.GUI; using FlaxEngine.Utilities; using FlaxEditor.Windows; @@ -203,6 +203,22 @@ namespace FlaxEditor.Utilities } } + /// + /// Clones the value. handles non-value types (such as arrays) that need deep value cloning. + /// + /// The source value to clone. + /// The duplicated value. + internal static object CloneValue(object value) + { + // For objects (eg. arrays) we need to clone them to prevent editing default/reference value within editor + if (value != null && (!value.GetType().IsValueType || !value.GetType().IsClass)) + { + var json = JsonSerializer.Serialize(value); + value = JsonSerializer.Deserialize(json, value.GetType()); + } + return value; + } + /// /// The colors for the keyframes used by the curve editor. /// From 33bd6a56cec0ef56da9aa3cb7737229aa722c4ef Mon Sep 17 00:00:00 2001 From: Norite SC <162097313+cNori@users.noreply.github.com> Date: Mon, 4 Mar 2024 19:15:15 +0100 Subject: [PATCH 36/76] vertex snapping refactor corrections --- Source/Editor/Gizmo/TransformGizmo.cs | 2 + Source/Editor/Gizmo/TransformGizmoBase.cs | 50 +++++++------------ .../SceneGraph/Actors/StaticModelNode.cs | 11 ++-- Source/Editor/SceneGraph/SceneGraphNode.cs | 21 ++------ 4 files changed, 32 insertions(+), 52 deletions(-) diff --git a/Source/Editor/Gizmo/TransformGizmo.cs b/Source/Editor/Gizmo/TransformGizmo.cs index 4918743ce..64f969990 100644 --- a/Source/Editor/Gizmo/TransformGizmo.cs +++ b/Source/Editor/Gizmo/TransformGizmo.cs @@ -263,6 +263,8 @@ namespace FlaxEditor.Gizmo // Note: because selection may contain objects and their children we have to split them and get only parents. // Later during transformation we apply translation/scale/rotation only on them (children inherit transformations) SceneGraphTools.BuildNodesParents(_selection, _selectionParents); + + base.OnSelectionChanged(newSelection); } /// diff --git a/Source/Editor/Gizmo/TransformGizmoBase.cs b/Source/Editor/Gizmo/TransformGizmoBase.cs index 99b1c8122..290795fa0 100644 --- a/Source/Editor/Gizmo/TransformGizmoBase.cs +++ b/Source/Editor/Gizmo/TransformGizmoBase.cs @@ -85,6 +85,7 @@ namespace FlaxEditor.Gizmo { InitDrawing(); ModeChanged += ResetTranslationScale; + } /// @@ -437,8 +438,6 @@ namespace FlaxEditor.Gizmo { case Mode.Translate: UpdateTranslateScale(); - if (Owner.SnapToVertex) - UpdateVertexSnapping(); break; case Mode.Scale: UpdateTranslateScale(); @@ -447,6 +446,8 @@ namespace FlaxEditor.Gizmo UpdateRotate(dt); break; } + if (Owner.SnapToVertex) + UpdateVertexSnapping(); } else { @@ -553,43 +554,23 @@ namespace FlaxEditor.Gizmo for (int i = 0; i < SelectionCount; i++) { var obj = GetSelectedObject(i); - if (obj.CanVertexSnap && obj.RayCastSelf(ref ray, out var distance, out _) && distance < closestDistance) + if (obj.RayCastSelf(ref ray, out var distance, out _) && distance < closestDistance) { closestDistance = distance; closestObject = obj; } } if (closestObject == null) - { - // Find the closest object in selection (in case ray didn't hit anything) - for (int i = 0; i < SelectionCount; i++) - { - var obj = GetSelectedObject(i); - if (obj.CanVertexSnap) - { - GetSelectedObjectsBounds(out var bounds, out _); - CollisionsHelper.ClosestPointBoxPoint(ref bounds, ref ray.Ray.Position, out var point); - var distance = Vector3.Distance(ref point, ref ray.Ray.Position); - if (distance < closestDistance) - { - closestDistance = distance; - closestObject = obj; - } - } - } - } + return; // ignore it if there is nothing under the mouse closestObject is only null if ray caster missed everything or Selection Count == 0 + _vertexSnapObject = closestObject; - if (closestObject == null) - return; - // Find the closest vertex to bounding box point (collision detection approximation) - var closestPoint = ray.Ray.GetPoint(closestDistance); - if (!closestObject.OnVertexSnap(ref closestPoint, out _vertexSnapPoint)) + if (!closestObject.OnVertexSnap(ref ray.Ray, closestDistance, out _vertexSnapPoint)) { - // Failed to get the closest point - _vertexSnapPoint = closestPoint; + //The OnVertexSnap is unimplemented or failed to get point return because there is nothing to do + _vertexSnapPoint = Vector3.Zero; + return; } - // Transform back to the local space of the object to work when moving it _vertexSnapPoint = closestObject.Transform.WorldToLocal(_vertexSnapPoint); } @@ -619,10 +600,9 @@ namespace FlaxEditor.Gizmo for (int i = 0; i < SelectionCount; i++) rayCast.ExcludeObjects.Add(GetSelectedObject(i)); var hit = Owner.SceneGraphRoot.RayCast(ref rayCast, out var distance, out var _); - if (hit != null && hit.CanVertexSnap) + if (hit != null) { - var point = rayCast.Ray.GetPoint(distance); - if (hit.OnVertexSnap(ref point, out var pointSnapped) + if (hit.OnVertexSnap(ref rayCast.Ray, distance, out var pointSnapped) //&& Vector3.Distance(point, pointSnapped) <= 25.0f ) { @@ -712,5 +692,11 @@ namespace FlaxEditor.Gizmo protected virtual void OnDuplicate() { } + /// + public override void OnSelectionChanged(List newSelection) + { + EndVertexSnapping(); + UpdateGizmoPosition(); + } } } diff --git a/Source/Editor/SceneGraph/Actors/StaticModelNode.cs b/Source/Editor/SceneGraph/Actors/StaticModelNode.cs index f1ab36041..0a8fd48bb 100644 --- a/Source/Editor/SceneGraph/Actors/StaticModelNode.cs +++ b/Source/Editor/SceneGraph/Actors/StaticModelNode.cs @@ -23,20 +23,23 @@ namespace FlaxEditor.SceneGraph.Actors : base(actor) { } + /// - public override bool OnVertexSnap(ref Vector3 point, out Vector3 result) + public override bool OnVertexSnap(ref Ray ray, float hitDistance, out Vector3 result) { - result = point; + // Find the closest vertex to bounding box point (collision detection approximation) + + result = ray.GetPoint(hitDistance); var model = ((StaticModel)Actor).Model; if (model && !model.WaitForLoaded()) { // TODO: move to C++ and use cached vertex buffer internally inside the Mesh if (_vertices == null) _vertices = new(); - var pointLocal = (Float3)Actor.Transform.WorldToLocal(point); + var pointLocal = (Float3)Actor.Transform.WorldToLocal(result); var minDistance = float.MaxValue; - foreach (var lod in model.LODs) + foreach (var lod in model.LODs) //[ToDo] fix it [Nori_SC note] this is wrong it should get current lod level going it throw all lods will create ghost snaping points { var hit = false; foreach (var mesh in lod.Meshes) diff --git a/Source/Editor/SceneGraph/SceneGraphNode.cs b/Source/Editor/SceneGraph/SceneGraphNode.cs index 5cf28af37..e9020592d 100644 --- a/Source/Editor/SceneGraph/SceneGraphNode.cs +++ b/Source/Editor/SceneGraph/SceneGraphNode.cs @@ -94,18 +94,6 @@ namespace FlaxEditor.SceneGraph /// public virtual bool CanTransform => true; - /// - /// Gets a value indicating whether this node can be used for the vertex snapping feature. - /// - public bool CanVertexSnap - { - get - { - var v = Vector3.Zero; - return OnVertexSnap(ref v, out _); - } - } - /// /// Gets a value indicating whether this is active. /// @@ -365,14 +353,15 @@ namespace FlaxEditor.SceneGraph } /// - /// Performs the vertex snapping of a given point on the object surface that is closest to a given location. + /// Performs the vertex snapping for a given ray and hitDistance. /// - /// The position to snap. + /// Raycasted ray + /// Hit distance from ray to object bounding box /// The result point on the object mesh that is closest to the specified location. /// True if got a valid result value, otherwise false (eg. if missing data or not initialized). - public virtual bool OnVertexSnap(ref Vector3 point, out Vector3 result) + public virtual bool OnVertexSnap(ref Ray ray,float hitDistance, out Vector3 result) // [NoriSC note] ray and hit Distance will be needed for a future use, in mesh types for proper collision detection { - result = point; + result = Vector3.Zero; return false; } From 25c1fcbf5195d060244ddfcb6ea78d3dba611a0e Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Mon, 4 Mar 2024 20:48:11 +0200 Subject: [PATCH 37/76] Add support for VS 2022 v17.10 / MSVC 14.4x toolset --- Source/Tools/Flax.Build/Platforms/GDK/GDKPlatform.cs | 5 ++++- Source/Tools/Flax.Build/Platforms/UWP/UWPPlatform.cs | 5 ++++- .../Flax.Build/Platforms/Windows/WindowsPlatform.cs | 3 ++- .../Platforms/Windows/WindowsPlatformBase.cs | 11 +++++++++++ .../Platforms/Windows/WindowsToolchainBase.cs | 11 ++++++++++- 5 files changed, 31 insertions(+), 4 deletions(-) diff --git a/Source/Tools/Flax.Build/Platforms/GDK/GDKPlatform.cs b/Source/Tools/Flax.Build/Platforms/GDK/GDKPlatform.cs index 97b619ae0..3a2cfeba7 100644 --- a/Source/Tools/Flax.Build/Platforms/GDK/GDKPlatform.cs +++ b/Source/Tools/Flax.Build/Platforms/GDK/GDKPlatform.cs @@ -31,8 +31,11 @@ namespace Flax.Build.Platforms var toolsets = GetToolsets(); if (!toolsets.ContainsKey(WindowsPlatformToolset.v141) && !toolsets.ContainsKey(WindowsPlatformToolset.v142) && - !toolsets.ContainsKey(WindowsPlatformToolset.v143)) + !toolsets.ContainsKey(WindowsPlatformToolset.v143) && + !toolsets.ContainsKey(WindowsPlatformToolset.v144)) + { _hasRequiredSDKsInstalled = false; + } } } } diff --git a/Source/Tools/Flax.Build/Platforms/UWP/UWPPlatform.cs b/Source/Tools/Flax.Build/Platforms/UWP/UWPPlatform.cs index b017b9175..ded484dc5 100644 --- a/Source/Tools/Flax.Build/Platforms/UWP/UWPPlatform.cs +++ b/Source/Tools/Flax.Build/Platforms/UWP/UWPPlatform.cs @@ -45,8 +45,11 @@ namespace Flax.Build.Platforms var toolsets = GetToolsets(); if (!toolsets.ContainsKey(WindowsPlatformToolset.v141) && !toolsets.ContainsKey(WindowsPlatformToolset.v142) && - !toolsets.ContainsKey(WindowsPlatformToolset.v143)) + !toolsets.ContainsKey(WindowsPlatformToolset.v143) && + !toolsets.ContainsKey(WindowsPlatformToolset.v144)) + { _hasRequiredSDKsInstalled = false; + } } /// diff --git a/Source/Tools/Flax.Build/Platforms/Windows/WindowsPlatform.cs b/Source/Tools/Flax.Build/Platforms/Windows/WindowsPlatform.cs index 4bdf02811..bfae02224 100644 --- a/Source/Tools/Flax.Build/Platforms/Windows/WindowsPlatform.cs +++ b/Source/Tools/Flax.Build/Platforms/Windows/WindowsPlatform.cs @@ -39,7 +39,8 @@ namespace Flax.Build.Platforms if (!toolsets.ContainsKey(WindowsPlatformToolset.v140) && !toolsets.ContainsKey(WindowsPlatformToolset.v141) && !toolsets.ContainsKey(WindowsPlatformToolset.v142) && - !toolsets.ContainsKey(WindowsPlatformToolset.v143)) + !toolsets.ContainsKey(WindowsPlatformToolset.v143) && + !toolsets.ContainsKey(WindowsPlatformToolset.v144)) { Log.Warning("Missing MSVC toolset v140 or later (VS 2015 or later C++ build tools). Cannot build for Windows platform."); _hasRequiredSDKsInstalled = false; diff --git a/Source/Tools/Flax.Build/Platforms/Windows/WindowsPlatformBase.cs b/Source/Tools/Flax.Build/Platforms/Windows/WindowsPlatformBase.cs index 6f4dec915..f038b1e6e 100644 --- a/Source/Tools/Flax.Build/Platforms/Windows/WindowsPlatformBase.cs +++ b/Source/Tools/Flax.Build/Platforms/Windows/WindowsPlatformBase.cs @@ -49,6 +49,11 @@ namespace Flax.Build.Platforms /// Visual Studio 2022 /// v143 = 143, + + /// + /// Visual Studio 2022 (v17.10 and later) + /// + v144 = 144, } /// @@ -240,6 +245,10 @@ namespace Flax.Build.Platforms _toolsets[WindowsPlatformToolset.v142] = toolset; else if (version.Major == 14 && version.Minor / 10 == 3) _toolsets[WindowsPlatformToolset.v143] = toolset; + else if (version.Major == 14 && version.Minor / 10 == 4) + _toolsets[WindowsPlatformToolset.v144] = toolset; + else + Log.Warning("Found Unsupported MSVC toolset version: " + version); } } } @@ -424,6 +433,7 @@ namespace Flax.Build.Platforms case WindowsPlatformToolset.v141: case WindowsPlatformToolset.v142: case WindowsPlatformToolset.v143: + case WindowsPlatformToolset.v144: { /* string crossCompilerPath = Path.Combine(vcToolChainDir, "bin", "HostX64", "x86", "cl.exe"); @@ -477,6 +487,7 @@ namespace Flax.Build.Platforms case WindowsPlatformToolset.v141: case WindowsPlatformToolset.v142: case WindowsPlatformToolset.v143: + case WindowsPlatformToolset.v144: { string nativeCompilerPath = Path.Combine(vcToolChainDir, "bin", "HostX64", "x64", "cl.exe"); if (File.Exists(nativeCompilerPath)) diff --git a/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchainBase.cs b/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchainBase.cs index 587467908..1c1f95afe 100644 --- a/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchainBase.cs +++ b/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchainBase.cs @@ -90,7 +90,14 @@ namespace Flax.Build.Platforms { if (VisualStudioInstance.HasIDE(VisualStudioVersion.VisualStudio2022)) { - toolsetVer = WindowsPlatformToolset.v143; + if (toolsets.Keys.Contains(WindowsPlatformToolset.v144)) + { + toolsetVer = WindowsPlatformToolset.v144; + } + else + { + toolsetVer = WindowsPlatformToolset.v143; + } } else if (VisualStudioInstance.HasIDE(VisualStudioVersion.VisualStudio2019)) { @@ -199,6 +206,7 @@ namespace Flax.Build.Platforms case WindowsPlatformToolset.v141: case WindowsPlatformToolset.v142: case WindowsPlatformToolset.v143: + case WindowsPlatformToolset.v144: { switch (Architecture) { @@ -378,6 +386,7 @@ namespace Flax.Build.Platforms var vcToolChainDir = toolsets[Toolset]; switch (Toolset) { + case WindowsPlatformToolset.v144: case WindowsPlatformToolset.v143: case WindowsPlatformToolset.v142: case WindowsPlatformToolset.v141: return Path.Combine(vcToolChainDir, "lib", "x86", "store", "references"); From 32501101b167f28f5633348cb5d5f359774da978 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 4 Mar 2024 20:27:11 +0100 Subject: [PATCH 38/76] Cleanup comments and implement `todo` to use LOD0 only #2299 --- Source/Editor/Gizmo/TransformGizmoBase.cs | 10 +++++----- Source/Editor/SceneGraph/Actors/StaticModelNode.cs | 6 +++--- Source/Editor/SceneGraph/SceneGraphNode.cs | 6 +++--- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Source/Editor/Gizmo/TransformGizmoBase.cs b/Source/Editor/Gizmo/TransformGizmoBase.cs index 290795fa0..81f76d392 100644 --- a/Source/Editor/Gizmo/TransformGizmoBase.cs +++ b/Source/Editor/Gizmo/TransformGizmoBase.cs @@ -85,7 +85,6 @@ namespace FlaxEditor.Gizmo { InitDrawing(); ModeChanged += ResetTranslationScale; - } /// @@ -564,13 +563,13 @@ namespace FlaxEditor.Gizmo return; // ignore it if there is nothing under the mouse closestObject is only null if ray caster missed everything or Selection Count == 0 _vertexSnapObject = closestObject; - if (!closestObject.OnVertexSnap(ref ray.Ray, closestDistance, out _vertexSnapPoint)) { - //The OnVertexSnap is unimplemented or failed to get point return because there is nothing to do + // The OnVertexSnap is unimplemented or failed to get point return because there is nothing to do _vertexSnapPoint = Vector3.Zero; return; } + // Transform back to the local space of the object to work when moving it _vertexSnapPoint = closestObject.Transform.WorldToLocal(_vertexSnapPoint); } @@ -602,9 +601,9 @@ namespace FlaxEditor.Gizmo var hit = Owner.SceneGraphRoot.RayCast(ref rayCast, out var distance, out var _); if (hit != null) { - if (hit.OnVertexSnap(ref rayCast.Ray, distance, out var pointSnapped) + if (hit.OnVertexSnap(ref rayCast.Ray, distance, out var pointSnapped) //&& Vector3.Distance(point, pointSnapped) <= 25.0f - ) + ) { _vertexSnapObjectTo = hit; _vertexSnapPointTo = hit.Transform.WorldToLocal(pointSnapped); @@ -692,6 +691,7 @@ namespace FlaxEditor.Gizmo protected virtual void OnDuplicate() { } + /// public override void OnSelectionChanged(List newSelection) { diff --git a/Source/Editor/SceneGraph/Actors/StaticModelNode.cs b/Source/Editor/SceneGraph/Actors/StaticModelNode.cs index 0a8fd48bb..1c09691e5 100644 --- a/Source/Editor/SceneGraph/Actors/StaticModelNode.cs +++ b/Source/Editor/SceneGraph/Actors/StaticModelNode.cs @@ -23,13 +23,12 @@ namespace FlaxEditor.SceneGraph.Actors : base(actor) { } - + /// public override bool OnVertexSnap(ref Ray ray, float hitDistance, out Vector3 result) { // Find the closest vertex to bounding box point (collision detection approximation) - result = ray.GetPoint(hitDistance); var model = ((StaticModel)Actor).Model; if (model && !model.WaitForLoaded()) @@ -39,7 +38,8 @@ namespace FlaxEditor.SceneGraph.Actors _vertices = new(); var pointLocal = (Float3)Actor.Transform.WorldToLocal(result); var minDistance = float.MaxValue; - foreach (var lod in model.LODs) //[ToDo] fix it [Nori_SC note] this is wrong it should get current lod level going it throw all lods will create ghost snaping points + var lodIndex = 0; // TODO: use LOD index based on the game view + var lod = model.LODs[lodIndex]; { var hit = false; foreach (var mesh in lod.Meshes) diff --git a/Source/Editor/SceneGraph/SceneGraphNode.cs b/Source/Editor/SceneGraph/SceneGraphNode.cs index e9020592d..dd6a4f9fe 100644 --- a/Source/Editor/SceneGraph/SceneGraphNode.cs +++ b/Source/Editor/SceneGraph/SceneGraphNode.cs @@ -355,11 +355,11 @@ namespace FlaxEditor.SceneGraph /// /// Performs the vertex snapping for a given ray and hitDistance. /// - /// Raycasted ray - /// Hit distance from ray to object bounding box + /// The ray to raycast. + /// Hit distance from ray to object bounding box. /// The result point on the object mesh that is closest to the specified location. /// True if got a valid result value, otherwise false (eg. if missing data or not initialized). - public virtual bool OnVertexSnap(ref Ray ray,float hitDistance, out Vector3 result) // [NoriSC note] ray and hit Distance will be needed for a future use, in mesh types for proper collision detection + public virtual bool OnVertexSnap(ref Ray ray, float hitDistance, out Vector3 result) { result = Vector3.Zero; return false; From 21edb43bb1a72773c86d4b09ea3295b2584a555d Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 4 Mar 2024 21:04:15 +0100 Subject: [PATCH 39/76] Fix creating prefab out of actor to reset local transform of it for better instancing #1545 --- Source/Editor/Content/Proxy/PrefabProxy.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Source/Editor/Content/Proxy/PrefabProxy.cs b/Source/Editor/Content/Proxy/PrefabProxy.cs index 0c0114fa8..ee3674daa 100644 --- a/Source/Editor/Content/Proxy/PrefabProxy.cs +++ b/Source/Editor/Content/Proxy/PrefabProxy.cs @@ -75,6 +75,8 @@ namespace FlaxEditor.Content /// public override void Create(string outputPath, object arg) { + bool resetTransform = false; + var transform = Transform.Identity; if (!(arg is Actor actor)) { // Create default prefab root object @@ -86,8 +88,17 @@ namespace FlaxEditor.Content // Cleanup it after usage Object.Destroy(actor, 20.0f); } + else if (actor.Scene != null) + { + // Create prefab with identity transform so the actor instance on a level will have it customized + resetTransform = true; + transform = actor.LocalTransform; + actor.LocalTransform = Transform.Identity; + } PrefabManager.CreatePrefab(actor, outputPath, true); + if (resetTransform) + actor.LocalTransform = transform; } /// From 0604a0393dc534bcd9793fb19368633ded32388c Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 4 Mar 2024 21:20:25 +0100 Subject: [PATCH 40/76] Fix error in Editor when Audio Clip duration is very small #2286 --- Source/Editor/Viewport/Previews/AudioClipPreview.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/Editor/Viewport/Previews/AudioClipPreview.cs b/Source/Editor/Viewport/Previews/AudioClipPreview.cs index 35d8477ee..11f291b5e 100644 --- a/Source/Editor/Viewport/Previews/AudioClipPreview.cs +++ b/Source/Editor/Viewport/Previews/AudioClipPreview.cs @@ -184,6 +184,7 @@ namespace FlaxEditor.Viewport.Previews break; default: throw new ArgumentOutOfRangeException(); } + samplesPerIndex = Math.Max(samplesPerIndex, info.NumChannels); const uint maxSamplesPerIndex = 64; uint samplesPerIndexDiff = Math.Max(1, samplesPerIndex / Math.Min(samplesPerIndex, maxSamplesPerIndex)); From e4583907e2a99cf892152a15c97327676289408b Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 4 Mar 2024 21:30:04 +0100 Subject: [PATCH 41/76] Fix build regression from #2299 --- Source/Editor/SceneGraph/Actors/StaticModelNode.cs | 11 ++++++++--- Source/Editor/SceneGraph/SceneGraphNode.cs | 2 +- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Source/Editor/SceneGraph/Actors/StaticModelNode.cs b/Source/Editor/SceneGraph/Actors/StaticModelNode.cs index 1c09691e5..60be5bef9 100644 --- a/Source/Editor/SceneGraph/Actors/StaticModelNode.cs +++ b/Source/Editor/SceneGraph/Actors/StaticModelNode.cs @@ -1,5 +1,11 @@ // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. +#if USE_LARGE_WORLDS +using Real = System.Double; +#else +using Real = System.Single; +#endif + using System; using System.Collections.Generic; using FlaxEditor.Content; @@ -24,9 +30,8 @@ namespace FlaxEditor.SceneGraph.Actors { } - /// - public override bool OnVertexSnap(ref Ray ray, float hitDistance, out Vector3 result) + public override bool OnVertexSnap(ref Ray ray, Real hitDistance, out Vector3 result) { // Find the closest vertex to bounding box point (collision detection approximation) result = ray.GetPoint(hitDistance); @@ -37,7 +42,7 @@ namespace FlaxEditor.SceneGraph.Actors if (_vertices == null) _vertices = new(); var pointLocal = (Float3)Actor.Transform.WorldToLocal(result); - var minDistance = float.MaxValue; + var minDistance = Real.MaxValue; var lodIndex = 0; // TODO: use LOD index based on the game view var lod = model.LODs[lodIndex]; { diff --git a/Source/Editor/SceneGraph/SceneGraphNode.cs b/Source/Editor/SceneGraph/SceneGraphNode.cs index dd6a4f9fe..b786efb7c 100644 --- a/Source/Editor/SceneGraph/SceneGraphNode.cs +++ b/Source/Editor/SceneGraph/SceneGraphNode.cs @@ -359,7 +359,7 @@ namespace FlaxEditor.SceneGraph /// Hit distance from ray to object bounding box. /// The result point on the object mesh that is closest to the specified location. /// True if got a valid result value, otherwise false (eg. if missing data or not initialized). - public virtual bool OnVertexSnap(ref Ray ray, float hitDistance, out Vector3 result) + public virtual bool OnVertexSnap(ref Ray ray, Real hitDistance, out Vector3 result) { result = Vector3.Zero; return false; From e20ddc83d8ae9dd0a1e50efcd6f62849deb60069 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 4 Mar 2024 21:53:28 +0100 Subject: [PATCH 42/76] Fix vehicle center of mass rotation used in wheels setup #2171 --- Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp index 496b9f057..0c77738f6 100644 --- a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp +++ b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp @@ -3469,7 +3469,8 @@ void* PhysicsBackend::CreateVehicle(WheeledVehicle* actor) Array> shapes; shapes.Resize(actorPhysX->getNbShapes()); actorPhysX->getShapes(shapes.Get(), shapes.Count(), 0); - const PxTransform centerOfMassOffset = actorPhysX->getCMassLocalPose(); + PxTransform centerOfMassOffset = actorPhysX->getCMassLocalPose(); + centerOfMassOffset.q = PxQuat(PxIdentity); // Initialize wheels simulation data PxVec3 offsets[PX_MAX_NB_WHEELS]; From 5ec737ef800a4a89f5358172311b71e86f64d068 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 4 Mar 2024 22:46:11 +0100 Subject: [PATCH 43/76] Fix loading old terrain heightmaps when using #2262 --- Source/Engine/Terrain/TerrainPatch.cpp | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/Source/Engine/Terrain/TerrainPatch.cpp b/Source/Engine/Terrain/TerrainPatch.cpp index 651d0305e..e8075314c 100644 --- a/Source/Engine/Terrain/TerrainPatch.cpp +++ b/Source/Engine/Terrain/TerrainPatch.cpp @@ -38,8 +38,18 @@ #define TERRAIN_PATCH_COLLISION_QUANTIZATION ((float)0x7fff) +// [Deprecated on 4.03.2024, expires on 4.03.2034] +struct TerrainCollisionDataHeaderOld +{ + int32 LOD; + float ScaleXZ; +}; + struct TerrainCollisionDataHeader { + static constexpr int32 CurrentVersion = 1; + int32 CheckOldMagicNumber; // Used to detect if loading new header or old one + int32 Version; int32 LOD; float ScaleXZ; }; @@ -685,13 +695,13 @@ bool CookCollision(TerrainDataUpdateInfo& info, TextureBase::InitData* initData, // Cook height field MemoryWriteStream outputStream; if (CollisionCooking::CookHeightField(heightFieldSize, heightFieldSize, heightFieldData, outputStream)) - { return true; - } // Write results collisionData->Resize(sizeof(TerrainCollisionDataHeader) + outputStream.GetPosition(), false); const auto header = (TerrainCollisionDataHeader*)collisionData->Get(); + header->CheckOldMagicNumber = MAX_int32; + header->Version = TerrainCollisionDataHeader::CurrentVersion; header->LOD = collisionLOD; header->ScaleXZ = (float)info.HeightmapSize / heightFieldSize; Platform::MemoryCopy(collisionData->Get() + sizeof(TerrainCollisionDataHeader), outputStream.GetHandle(), outputStream.GetPosition()); @@ -2133,7 +2143,15 @@ bool TerrainPatch::CreateHeightField() return true; } - const auto collisionHeader = (TerrainCollisionDataHeader*)_heightfield->Data.Get(); + // Check if the cooked collision matches the engine version + auto collisionHeader = (TerrainCollisionDataHeader*)_heightfield->Data.Get(); + if (collisionHeader->CheckOldMagicNumber != MAX_int32 || collisionHeader->CurrentVersion != TerrainCollisionDataHeader::CurrentVersion) + { + // Reset height map + return InitializeHeightMap(); + } + + // Create heightfield object from the data _collisionScaleXZ = collisionHeader->ScaleXZ * TERRAIN_UNITS_PER_VERTEX; _physicsHeightField = PhysicsBackend::CreateHeightField(_heightfield->Data.Get() + sizeof(TerrainCollisionDataHeader), _heightfield->Data.Count() - sizeof(TerrainCollisionDataHeader)); if (_physicsHeightField == nullptr) From d4e0023925f2f4df79038d2ae1391ae2292baa04 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 4 Mar 2024 22:46:30 +0100 Subject: [PATCH 44/76] Fix `RawDataAsset` memory usage to use capacity of the bytes array instead of just size --- Source/Engine/Content/Assets/RawDataAsset.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Content/Assets/RawDataAsset.cpp b/Source/Engine/Content/Assets/RawDataAsset.cpp index a0a677cde..f567abb1f 100644 --- a/Source/Engine/Content/Assets/RawDataAsset.cpp +++ b/Source/Engine/Content/Assets/RawDataAsset.cpp @@ -69,7 +69,7 @@ uint64 RawDataAsset::GetMemoryUsage() const Locker.Lock(); uint64 result = BinaryAsset::GetMemoryUsage(); result += sizeof(RawDataAsset) - sizeof(BinaryAsset); - result += Data.Count(); + result += Data.Capacity(); Locker.Unlock(); return result; } From 8964b8907c538967b32ae8dd4c83dc22569e34c4 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 5 Mar 2024 11:10:37 +0100 Subject: [PATCH 45/76] Fix to 5ec737ef800a4a89f5358172311b71e86f64d068 --- Source/Engine/Terrain/TerrainPatch.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Terrain/TerrainPatch.cpp b/Source/Engine/Terrain/TerrainPatch.cpp index e8075314c..053ab10e0 100644 --- a/Source/Engine/Terrain/TerrainPatch.cpp +++ b/Source/Engine/Terrain/TerrainPatch.cpp @@ -38,7 +38,7 @@ #define TERRAIN_PATCH_COLLISION_QUANTIZATION ((float)0x7fff) -// [Deprecated on 4.03.2024, expires on 4.03.2034] +// [Deprecated on 4.03.2024, expires on 4.03.2029] struct TerrainCollisionDataHeaderOld { int32 LOD; @@ -2145,7 +2145,7 @@ bool TerrainPatch::CreateHeightField() // Check if the cooked collision matches the engine version auto collisionHeader = (TerrainCollisionDataHeader*)_heightfield->Data.Get(); - if (collisionHeader->CheckOldMagicNumber != MAX_int32 || collisionHeader->CurrentVersion != TerrainCollisionDataHeader::CurrentVersion) + if (collisionHeader->CheckOldMagicNumber != MAX_int32 || collisionHeader->Version != TerrainCollisionDataHeader::CurrentVersion) { // Reset height map return InitializeHeightMap(); From 557d39aea45e59b53d0f2e334b5a3b35947a0b74 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 5 Mar 2024 11:19:00 +0100 Subject: [PATCH 46/76] Fix `Mad` node to not go above `Multiply` in Visject --- Source/Editor/Surface/Archetypes/Math.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/Editor/Surface/Archetypes/Math.cs b/Source/Editor/Surface/Archetypes/Math.cs index 65e0f0195..9c1d00196 100644 --- a/Source/Editor/Surface/Archetypes/Math.cs +++ b/Source/Editor/Surface/Archetypes/Math.cs @@ -239,6 +239,7 @@ namespace FlaxEditor.Surface.Archetypes ConnectionsHints = ConnectionsHint.Numeric, IndependentBoxes = new[] { 0, 1, 2 }, DependentBoxes = new[] { 3 }, + SortScore = -1, // Lower sort score to not go above Multiply node DefaultValues = new object[] { 1.0f, From b506295b7b1d45e7f3a6caa7804d31ec66d50046 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 5 Mar 2024 12:31:03 +0100 Subject: [PATCH 47/76] Fix loading `BehaviorKnowledgeSelectorAny` from json object --- Source/Engine/Serialization/JsonConverters.cs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/Source/Engine/Serialization/JsonConverters.cs b/Source/Engine/Serialization/JsonConverters.cs index e171df4bd..c7d937abc 100644 --- a/Source/Engine/Serialization/JsonConverters.cs +++ b/Source/Engine/Serialization/JsonConverters.cs @@ -156,6 +156,29 @@ namespace FlaxEngine.Json var result = new BehaviorKnowledgeSelectorAny(); if (reader.TokenType == JsonToken.String) result.Path = (string)reader.Value; + else if (reader.TokenType == JsonToken.StartObject) + { + while (reader.Read()) + { + switch (reader.TokenType) + { + case JsonToken.PropertyName: + { + var propertyName = (string)reader.Value; + switch (propertyName) + { + case "Path": + result.Path = reader.ReadAsString(); + break; + } + break; + } + case JsonToken.Comment: break; + case JsonToken.String: break; + default: return result; + } + } + } return result; } From d5f4254a7314a9ef9ac930678d1faa2c04e4983c Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 5 Mar 2024 12:45:40 +0100 Subject: [PATCH 48/76] Add `BehaviorTreeKnowledgeBooleanDecorator` --- Source/Engine/AI/BehaviorTreeNodes.cpp | 8 ++++++++ Source/Engine/AI/BehaviorTreeNodes.h | 21 +++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/Source/Engine/AI/BehaviorTreeNodes.cpp b/Source/Engine/AI/BehaviorTreeNodes.cpp index a1e8089db..c6dbd461f 100644 --- a/Source/Engine/AI/BehaviorTreeNodes.cpp +++ b/Source/Engine/AI/BehaviorTreeNodes.cpp @@ -706,6 +706,14 @@ bool BehaviorTreeKnowledgeValuesConditionalDecorator::CanUpdate(const BehaviorUp return BehaviorKnowledge::CompareValues((float)ValueA.Get(context.Knowledge), (float)ValueB.Get(context.Knowledge), Comparison); } +bool BehaviorTreeKnowledgeBooleanDecorator::CanUpdate(const BehaviorUpdateContext& context) +{ + Variant value = Value.Get(context.Knowledge); + bool result = (bool)value; + result ^= Invert; + return result; +} + bool BehaviorTreeHasTagDecorator::CanUpdate(const BehaviorUpdateContext& context) { bool result = false; diff --git a/Source/Engine/AI/BehaviorTreeNodes.h b/Source/Engine/AI/BehaviorTreeNodes.h index 2c2405c8c..af5191f2e 100644 --- a/Source/Engine/AI/BehaviorTreeNodes.h +++ b/Source/Engine/AI/BehaviorTreeNodes.h @@ -445,6 +445,27 @@ public: bool CanUpdate(const BehaviorUpdateContext& context) override; }; +/// +/// Checks certain knowledge value to conditionally enter the node if the value is set (eg. not-null object reference or boolean value). +/// +API_CLASS(Sealed) class FLAXENGINE_API BehaviorTreeKnowledgeBooleanDecorator : public BehaviorTreeDecorator +{ + DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(BehaviorTreeKnowledgeBooleanDecorator, BehaviorTreeDecorator); + API_AUTO_SERIALIZATION(); + + // The value from behavior's knowledge (blackboard, goal or sensor) to check if it's set (eg. not-null object reference or boolean value). + API_FIELD(Attributes="EditorOrder(0)") + BehaviorKnowledgeSelectorAny Value; + + // If checked, the condition will be inverted. + API_FIELD(Attributes="EditorOrder(10)") + bool Invert = false; + +public: + // [BehaviorTreeNode] + bool CanUpdate(const BehaviorUpdateContext& context) override; +}; + /// /// Checks if certain actor (from knowledge) has a given tag assigned. /// From f0c2e65b5cb578c39407761e5ad288a1a374ced1 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 5 Mar 2024 15:00:47 +0100 Subject: [PATCH 49/76] Fix incorrect mouse cursor hiding on Windows when window is not focused #1757 --- Source/Engine/Platform/Windows/WindowsWindow.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Platform/Windows/WindowsWindow.cpp b/Source/Engine/Platform/Windows/WindowsWindow.cpp index da2e491be..fcfdbeb23 100644 --- a/Source/Engine/Platform/Windows/WindowsWindow.cpp +++ b/Source/Engine/Platform/Windows/WindowsWindow.cpp @@ -772,7 +772,8 @@ void WindowsWindow::CheckForWindowResize() void WindowsWindow::UpdateCursor() const { - if (_cursor == CursorType::Hidden) + // Don't hide cursor when window is not focused + if (_cursor == CursorType::Hidden && _focused) { ::SetCursor(nullptr); return; @@ -1234,6 +1235,7 @@ LRESULT WindowsWindow::WndProc(UINT msg, WPARAM wParam, LPARAM lParam) break; case WM_SETFOCUS: OnGotFocus(); + UpdateCursor(); if (_isClippingCursor && !_clipCursorSet) { _clipCursorSet = true; @@ -1247,6 +1249,7 @@ LRESULT WindowsWindow::WndProc(UINT msg, WPARAM wParam, LPARAM lParam) ClipCursor(nullptr); } OnLostFocus(); + UpdateCursor(); break; case WM_ACTIVATEAPP: if (wParam == TRUE && !_focused) @@ -1261,6 +1264,7 @@ LRESULT WindowsWindow::WndProc(UINT msg, WPARAM wParam, LPARAM lParam) SetIsFullscreen(false); } } + UpdateCursor(); break; case WM_MENUCHAR: // A menu is active and the user presses a key that does not correspond to any mnemonic or accelerator key so just ignore and don't beep From 28da656ed13aacc5639d8a6e0d0038b73e1c3128 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 5 Mar 2024 16:30:50 +0100 Subject: [PATCH 50/76] Fix preserving actors hierarchy order when performing undo of actor removal #1751 --- .../CustomEditors/Dedicated/RagdollEditor.cs | 8 ++-- Source/Editor/Modules/SceneEditingModule.cs | 2 +- .../Editor/Undo/Actions/DeleteActorsAction.cs | 40 +++++++++++++++++-- .../Editor/Undo/Actions/PasteActorsAction.cs | 2 +- Source/Engine/Level/Actor.cpp | 8 ---- 5 files changed, 43 insertions(+), 17 deletions(-) diff --git a/Source/Editor/CustomEditors/Dedicated/RagdollEditor.cs b/Source/Editor/CustomEditors/Dedicated/RagdollEditor.cs index 175312b6a..17387a202 100644 --- a/Source/Editor/CustomEditors/Dedicated/RagdollEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/RagdollEditor.cs @@ -160,7 +160,7 @@ namespace FlaxEditor.CustomEditors.Dedicated var actions = new List(); foreach (var body in bodies) { - var action = new Actions.DeleteActorsAction(new List { SceneGraphFactory.FindNode(body.ID) }); + var action = new Actions.DeleteActorsAction(body); action.Do(); actions.Add(action); } @@ -185,7 +185,7 @@ namespace FlaxEditor.CustomEditors.Dedicated var body = bodies.FirstOrDefault(x => x.Name == name); if (body != null) { - var action = new Actions.DeleteActorsAction(new List { SceneGraphFactory.FindNode(body.ID) }); + var action = new Actions.DeleteActorsAction(body); action.Do(); Presenter.Undo?.AddAction(action); } @@ -224,7 +224,7 @@ namespace FlaxEditor.CustomEditors.Dedicated else { // Remove joint that will no longer be valid - var action = new Actions.DeleteActorsAction(new List { SceneGraphFactory.FindNode(joint.ID) }); + var action = new Actions.DeleteActorsAction(joint); action.Do(); Presenter.Undo?.AddAction(action); } @@ -233,7 +233,7 @@ namespace FlaxEditor.CustomEditors.Dedicated // Remove body { - var action = new Actions.DeleteActorsAction(new List { SceneGraphFactory.FindNode(body.ID) }); + var action = new Actions.DeleteActorsAction(body); action.Do(); Presenter.Undo?.AddAction(action); } diff --git a/Source/Editor/Modules/SceneEditingModule.cs b/Source/Editor/Modules/SceneEditingModule.cs index faeca2520..df9009cc8 100644 --- a/Source/Editor/Modules/SceneEditingModule.cs +++ b/Source/Editor/Modules/SceneEditingModule.cs @@ -333,7 +333,7 @@ namespace FlaxEditor.Modules actorNode.PostSpawn(); // Create undo action - IUndoAction action = new DeleteActorsAction(new List(1) { actorNode }, true); + IUndoAction action = new DeleteActorsAction(actorNode, true); if (autoSelect) { var before = Selection.ToArray(); diff --git a/Source/Editor/Undo/Actions/DeleteActorsAction.cs b/Source/Editor/Undo/Actions/DeleteActorsAction.cs index 57352a12d..6b8ebee56 100644 --- a/Source/Editor/Undo/Actions/DeleteActorsAction.cs +++ b/Source/Editor/Undo/Actions/DeleteActorsAction.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; using FlaxEditor.SceneGraph; -using FlaxEditor.Scripting; using FlaxEngine; using FlaxEngine.Utilities; @@ -25,6 +24,9 @@ namespace FlaxEditor.Actions [Serialize] private Guid[] _nodeParentsIDs; + [Serialize] + private int[] _nodeParentsOrders; + [Serialize] private Guid[] _prefabIds; @@ -43,12 +45,35 @@ namespace FlaxEditor.Actions [Serialize] protected List _nodeParents; + /// + /// Initializes a new instance of the class. + /// + /// The actor. + /// If set to true action will be inverted - instead of delete it will create actors. + /// If set to true action will be preserve actors order when performing undo. + internal DeleteActorsAction(Actor actor, bool isInverted = false, bool preserveOrder = true) + : this(new List(1) { SceneGraphFactory.FindNode(actor.ID) }, isInverted, preserveOrder) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The object. + /// If set to true action will be inverted - instead of delete it will create actors. + /// If set to true action will be preserve actors order when performing undo. + internal DeleteActorsAction(SceneGraphNode node, bool isInverted = false, bool preserveOrder = true) + : this(new List(1) { node }, isInverted, preserveOrder) + { + } + /// /// Initializes a new instance of the class. /// /// The objects. - /// If set to true action will be inverted - instead of delete it will be create actors. - internal DeleteActorsAction(List nodes, bool isInverted = false) + /// If set to true action will be inverted - instead of delete it will create actors. + /// If set to true action will be preserve actors order when performing undo. + internal DeleteActorsAction(List nodes, bool isInverted = false, bool preserveOrder = true) { _isInverted = isInverted; @@ -82,6 +107,12 @@ namespace FlaxEditor.Actions _nodeParentsIDs = new Guid[_nodeParents.Count]; for (int i = 0; i < _nodeParentsIDs.Length; i++) _nodeParentsIDs[i] = _nodeParents[i].ID; + if (preserveOrder) + { + _nodeParentsOrders = new int[_nodeParents.Count]; + for (int i = 0; i < _nodeParentsOrders.Length; i++) + _nodeParentsOrders[i] = _nodeParents[i].OrderInParent; + } // Serialize actors _actorsData = Actor.ToBytes(actors.ToArray()); @@ -122,6 +153,7 @@ namespace FlaxEditor.Actions { _actorsData = null; _nodeParentsIDs = null; + _nodeParentsOrders = null; _prefabIds = null; _prefabObjectIds = null; _nodeParents.Clear(); @@ -211,6 +243,8 @@ namespace FlaxEditor.Actions if (foundNode is ActorNode node) { nodes.Add(node); + if (_nodeParentsOrders != null) + node.Actor.OrderInParent = _nodeParentsOrders[i]; } } nodes.BuildNodesParents(_nodeParents); diff --git a/Source/Editor/Undo/Actions/PasteActorsAction.cs b/Source/Editor/Undo/Actions/PasteActorsAction.cs index 68b2b755e..87a21ec2d 100644 --- a/Source/Editor/Undo/Actions/PasteActorsAction.cs +++ b/Source/Editor/Undo/Actions/PasteActorsAction.cs @@ -165,7 +165,7 @@ namespace FlaxEditor.Actions var child = children[j]; if (child != actor && child.Name == name) { - string newName = Utilities.Utils.IncrementNameNumber(name, x => IsNameValid(x)); + string newName = Utilities.Utils.IncrementNameNumber(name, IsNameValid); foundNamesResults[newName] = true; actor.Name = newName; // Multiple actors may have the same name, continue diff --git a/Source/Engine/Level/Actor.cpp b/Source/Engine/Level/Actor.cpp index 0e187ac7b..2615f09fa 100644 --- a/Source/Engine/Level/Actor.cpp +++ b/Source/Engine/Level/Actor.cpp @@ -1764,14 +1764,6 @@ bool Actor::FromBytes(const Span& data, Array& output, ISerializeM } Scripting::ObjectsLookupIdMapping.Set(nullptr); - // Link objects - //for (int32 i = 0; i < objectsCount; i++) - { - //SceneObject* obj = sceneObjects->At(i); - // TODO: post load or post spawn? - //obj->PostLoad(); - } - // Update objects order //for (int32 i = 0; i < objectsCount; i++) { From 2e305da286e194920c9e70652fc6265708416ce7 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 5 Mar 2024 16:31:14 +0100 Subject: [PATCH 51/76] Fix crash when scene graph node gets somehow duplicated by internal error in Editor --- Source/Editor/SceneGraph/SceneGraphNode.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Source/Editor/SceneGraph/SceneGraphNode.cs b/Source/Editor/SceneGraph/SceneGraphNode.cs index b786efb7c..5becb69dc 100644 --- a/Source/Editor/SceneGraph/SceneGraphNode.cs +++ b/Source/Editor/SceneGraph/SceneGraphNode.cs @@ -41,7 +41,11 @@ namespace FlaxEditor.SceneGraph protected SceneGraphNode(Guid id) { ID = id; - SceneGraphFactory.Nodes.Add(id, this); + if (SceneGraphFactory.Nodes.TryGetValue(id, out var duplicate) && duplicate != null) + { + Editor.LogWarning($"Duplicated Scene Graph node with ID {FlaxEngine.Json.JsonSerializer.GetStringID(id)} of type '{duplicate.GetType().FullName}'"); + } + SceneGraphFactory.Nodes[id] = this; } /// From 07e25bb24c0760015a34f15d271a6c4a67e4d9c9 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 6 Mar 2024 12:03:57 +0100 Subject: [PATCH 52/76] Fix drag&drop regression issue on tree UI --- Source/Editor/GUI/Tree/TreeNode.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Source/Editor/GUI/Tree/TreeNode.cs b/Source/Editor/GUI/Tree/TreeNode.cs index 3e1827438..3f341d713 100644 --- a/Source/Editor/GUI/Tree/TreeNode.cs +++ b/Source/Editor/GUI/Tree/TreeNode.cs @@ -42,6 +42,7 @@ namespace FlaxEditor.GUI.Tree private DragItemPositioning _dragOverMode; private bool _isDragOverHeader; + private static double _dragEndTime = -1000; /// /// Gets or sets the text. @@ -736,9 +737,8 @@ namespace FlaxEditor.GUI.Tree UpdateMouseOverFlags(location); // Clear flag for left button - if (button == MouseButton.Left) + if (button == MouseButton.Left && _isMouseDown) { - // Clear flag _isMouseDown = false; _mouseDownTime = -1; } @@ -746,6 +746,10 @@ namespace FlaxEditor.GUI.Tree // Check if mouse hits bar and node isn't a root if (_mouseOverHeader) { + // Skip mouse up event right after drag drop ends + if (button == MouseButton.Left && Platform.TimeSeconds - _dragEndTime <= 0.1f) + return true; + // Prevent from selecting node when user is just clicking at an arrow if (!_mouseOverArrow) { @@ -1006,6 +1010,7 @@ namespace FlaxEditor.GUI.Tree if (result == DragDropEffect.None) { UpdateDrawPositioning(ref location); + _dragEndTime = Platform.TimeSeconds; // Check if mouse is over header if (TestHeaderHit(ref location)) From c561d684eba1e0cea290b5d07a2b2603132bce72 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 6 Mar 2024 19:01:36 +0100 Subject: [PATCH 53/76] Refactor undo logic for actors reparenting in Editor #1741 --- Source/Editor/SceneGraph/ActorNode.cs | 36 +-- Source/Editor/SceneGraph/GUI/ActorTreeNode.cs | 221 +++++------------- .../Editor/Undo/Actions/ParentActorsAction.cs | 199 ++++++++++++++++ .../Editor/Undo/Actions/PasteActorsAction.cs | 1 - 4 files changed, 257 insertions(+), 200 deletions(-) create mode 100644 Source/Editor/Undo/Actions/ParentActorsAction.cs diff --git a/Source/Editor/SceneGraph/ActorNode.cs b/Source/Editor/SceneGraph/ActorNode.cs index 5c6adb055..91bf26103 100644 --- a/Source/Editor/SceneGraph/ActorNode.cs +++ b/Source/Editor/SceneGraph/ActorNode.cs @@ -316,41 +316,7 @@ namespace FlaxEditor.SceneGraph { base.OnParentChanged(); - // Update UI (special case if actor is spawned and added to existing scene tree) - var parentTreeNode = (parentNode as ActorNode)?.TreeNode; - if (parentTreeNode != null && !parentTreeNode.IsLayoutLocked) - { - parentTreeNode.IsLayoutLocked = true; - _treeNode.Parent = parentTreeNode; - _treeNode.IndexInParent = _actor.OrderInParent; - parentTreeNode.IsLayoutLocked = false; - - // Skip UI update if node won't be in a view - if (parentTreeNode.IsCollapsed) - { - TreeNode.UnlockChildrenRecursive(); - } - else - { - // Try to perform layout at the level where it makes it the most performant (the least computations) - var tree = parentTreeNode.ParentTree; - if (tree != null) - { - if (tree.Parent is FlaxEngine.GUI.Panel treeParent) - treeParent.PerformLayout(); - else - tree.PerformLayout(); - } - else - { - parentTreeNode.PerformLayout(); - } - } - } - else - { - _treeNode.Parent = parentTreeNode; - } + _treeNode.OnParentChanged(_actor, parentNode as ActorNode); } /// diff --git a/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs b/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs index 270647433..6056cf68b 100644 --- a/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs +++ b/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Linq; +using FlaxEditor.Actions; using FlaxEditor.Content; using FlaxEditor.GUI; using FlaxEditor.GUI.Drag; @@ -14,7 +15,6 @@ using FlaxEditor.Windows.Assets; using FlaxEngine; using FlaxEngine.GUI; using FlaxEngine.Utilities; -using Object = FlaxEngine.Object; namespace FlaxEditor.SceneGraph.GUI { @@ -82,8 +82,51 @@ namespace FlaxEditor.SceneGraph.GUI UpdateText(); } + internal void OnParentChanged(Actor actor, ActorNode parentNode) + { + // Update cached value + _orderInParent = actor.OrderInParent; + + // Update UI (special case if actor is spawned and added to existing scene tree) + var parentTreeNode = parentNode?.TreeNode; + if (parentTreeNode != null && !parentTreeNode.IsLayoutLocked) + { + parentTreeNode.IsLayoutLocked = true; + Parent = parentTreeNode; + IndexInParent = _orderInParent; + parentTreeNode.IsLayoutLocked = false; + + // Skip UI update if node won't be in a view + if (parentTreeNode.IsCollapsed) + { + UnlockChildrenRecursive(); + } + else + { + // Try to perform layout at the level where it makes it the most performant (the least computations) + var tree = parentTreeNode.ParentTree; + if (tree != null) + { + if (tree.Parent is Panel treeParent) + treeParent.PerformLayout(); + else + tree.PerformLayout(); + } + else + { + parentTreeNode.PerformLayout(); + } + } + } + else + { + Parent = parentTreeNode; + } + } + internal void OnOrderInParentChanged() { + // Use cached value to check if we need to update UI layout (and update siblings order at once) if (Parent is ActorTreeNode parent) { var anyChanged = false; @@ -419,134 +462,6 @@ namespace FlaxEditor.SceneGraph.GUI _dragHandlers.OnDragLeave(); } - [Serializable] - private class ReparentAction : IUndoAction - { - [Serialize] - private Guid[] _ids; - - [Serialize] - private int _actorsCount; - - [Serialize] - private Guid[] _prefabIds; - - [Serialize] - private Guid[] _prefabObjectIds; - - public ReparentAction(Actor actor) - : this(new List { actor }) - { - } - - public ReparentAction(List actors) - { - var allActors = new List(Mathf.NextPowerOfTwo(actors.Count)); - - for (int i = 0; i < actors.Count; i++) - { - GetAllActors(allActors, actors[i]); - } - - var allScripts = new List