diff --git a/Source/Editor/CustomEditors/CustomEditor.cs b/Source/Editor/CustomEditors/CustomEditor.cs index 0c739739c..ff9a14ea3 100644 --- a/Source/Editor/CustomEditors/CustomEditor.cs +++ b/Source/Editor/CustomEditors/CustomEditor.cs @@ -749,6 +749,15 @@ namespace FlaxEditor.CustomEditors } } + private Actor FindActor(CustomEditor editor) + { + if (editor.Values[0] is Actor actor) + return actor; + if (editor.ParentEditor != null) + return FindActor(editor.ParentEditor); + return null; + } + private Actor FindPrefabRoot(CustomEditor editor) { if (editor.Values[0] is Actor actor) @@ -767,32 +776,35 @@ namespace FlaxEditor.CustomEditors return FindPrefabRoot(actor.Parent); } - private SceneObject FindObjectWithPrefabObjectId(Actor actor, ref Guid prefabObjectId) + private SceneObject FindObjectWithPrefabObjectId(Actor actor, ref Guid prefabObjectId, Actor endPoint) { + var visited = new HashSet(); + return FindObjectWithPrefabObjectId(actor, ref prefabObjectId, endPoint, visited); + } + + private SceneObject FindObjectWithPrefabObjectId(Actor actor, ref Guid prefabObjectId, Actor endPoint, HashSet visited) + { + if (visited.Contains(actor) || actor is Scene || actor == endPoint) + return null; if (actor.PrefabObjectID == prefabObjectId) return actor; for (int i = 0; i < actor.ScriptsCount; i++) { - if (actor.GetScript(i).PrefabObjectID == prefabObjectId) - { - var a = actor.GetScript(i); - if (a != null) - return a; - } + var script = actor.GetScript(i); + if (script != null && script.PrefabObjectID == prefabObjectId) + return script; } for (int i = 0; i < actor.ChildrenCount; i++) { - if (actor.GetChild(i).PrefabObjectID == prefabObjectId) - { - var a = actor.GetChild(i); - if (a != null) - return a; - } + var child = actor.GetChild(i); + if (child != null && child.PrefabObjectID == prefabObjectId) + return child; } - return null; + // Go up in the hierarchy + return FindObjectWithPrefabObjectId(actor.Parent, ref prefabObjectId, endPoint, visited); } /// @@ -826,7 +838,7 @@ namespace FlaxEditor.CustomEditors } var prefabObjectId = referenceSceneObject.PrefabObjectID; - var prefabInstanceRef = FindObjectWithPrefabObjectId(prefabInstanceRoot, ref prefabObjectId); + var prefabInstanceRef = FindObjectWithPrefabObjectId(FindActor(this), ref prefabObjectId, prefabInstanceRoot); if (prefabInstanceRef == null) { Editor.LogWarning("Missing prefab instance reference in the prefab instance. Cannot revert to it."); diff --git a/Source/Editor/CustomEditors/CustomEditorPresenter.cs b/Source/Editor/CustomEditors/CustomEditorPresenter.cs index 1030abfda..ccf712904 100644 --- a/Source/Editor/CustomEditors/CustomEditorPresenter.cs +++ b/Source/Editor/CustomEditors/CustomEditorPresenter.cs @@ -63,6 +63,11 @@ namespace FlaxEditor.CustomEditors /// Indication of if the properties window is locked on specific objects. /// public bool LockSelection { get; set; } + + /// + /// Gets the scene editing context. + /// + public ISceneEditingContext SceneContext { get; } } /// diff --git a/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs b/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs index 540b602ec..d312f64b2 100644 --- a/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs @@ -1,8 +1,5 @@ // Copyright (c) Wojciech Figat. All rights reserved. -using System; -using System.Collections.Generic; -using System.Linq; using FlaxEditor.Actions; using FlaxEditor.CustomEditors.Editors; using FlaxEditor.CustomEditors.Elements; @@ -10,12 +7,14 @@ using FlaxEditor.GUI; using FlaxEditor.GUI.ContextMenu; using FlaxEditor.GUI.Tree; using FlaxEditor.Scripting; -using FlaxEditor.Windows; using FlaxEditor.Windows.Assets; using FlaxEngine; using FlaxEngine.GUI; using FlaxEngine.Json; using FlaxEngine.Utilities; +using System; +using System.Collections.Generic; +using System.Linq; namespace FlaxEditor.CustomEditors.Dedicated { @@ -240,6 +239,12 @@ namespace FlaxEditor.CustomEditors.Dedicated node.TextColor = Color.OrangeRed; node.Text = Utilities.Utils.GetPropertyNameUI(removed.PrefabObject.GetType().Name); } + // Removed Actor + else if (editor is RemovedActorDummy removedActor) + { + node.TextColor = Color.OrangeRed; + node.Text = $"{removedActor.PrefabObject.Name} ({Utilities.Utils.GetPropertyNameUI(removedActor.PrefabObject.GetType().Name)})"; + } // Actor or Script else if (editor.Values[0] is SceneObject sceneObject) { @@ -295,16 +300,40 @@ namespace FlaxEditor.CustomEditors.Dedicated // Not used } } + + private class RemovedActorDummy : CustomEditor + { + /// + /// The removed prefab object (from the prefab default instance). + /// + public Actor PrefabObject; + + /// + /// The prefab instance's parent. + /// + public Actor ParentActor; + + /// + /// The order of the removed actor in the parent. + /// + public int OrderInParent; + + /// + public override void Initialize(LayoutElementsContainer layout) + { + // Not used + } + } private TreeNode ProcessDiff(CustomEditor editor, bool skipIfNotModified = true) { - // Special case for new Script added to actor - if (editor.Values[0] is Script script && !script.HasPrefabLink) + // Special case for new Script or child actor added to actor + if ((editor.Values[0] is Script script && !script.HasPrefabLink) || (editor.Values[0] is Actor a && !a.HasPrefabLink)) return CreateDiffNode(editor); // Skip if no change detected var isRefEdited = editor.Values.IsReferenceValueModified; - if (!isRefEdited && skipIfNotModified) + if (!isRefEdited && skipIfNotModified && editor is not ScriptsEditor) return null; TreeNode result = null; @@ -359,6 +388,44 @@ namespace FlaxEditor.CustomEditors.Dedicated } } + // Compare child actors for removed actors. + if (editor is ActorEditor && editor.Values.HasReferenceValue && editor.Values.ReferenceValue is Actor prefabObjectActor) + { + var thisActor = editor.Values[0] as Actor; + for (int i = 0; i < prefabObjectActor.ChildrenCount; i++) + { + var prefabActorChild = prefabObjectActor.Children[i]; + if (thisActor == null) + continue; + bool isRemoved = true; + for (int j = 0; j < thisActor.ChildrenCount; j++) + { + var actorChild = thisActor.Children[j]; + if (actorChild.PrefabObjectID == prefabActorChild.PrefabObjectID) + { + isRemoved = false; + break; + } + } + if (isRemoved) + { + var dummy = new RemovedActorDummy + { + PrefabObject = prefabActorChild, + ParentActor = thisActor, + OrderInParent = prefabActorChild.OrderInParent, + }; + var child = CreateDiffNode(dummy); + if (result == null) + result = CreateDiffNode(editor); + result.AddChild(child); + } + } + } + + if (editor is ScriptsEditor && result != null && result.ChildrenCount == 0) + return null; + return result; } @@ -438,6 +505,15 @@ namespace FlaxEditor.CustomEditors.Dedicated Presenter.BuildLayoutOnUpdate(); } + private static void GetAllPrefabObjects(List objects, Actor actor) + { + objects.Add(actor); + objects.AddRange(actor.Scripts); + var children = actor.Children; + foreach (var child in children) + GetAllPrefabObjects(objects, child); + } + private void OnDiffRevert(CustomEditor editor) { // Special case for removed Script from actor @@ -459,6 +535,22 @@ namespace FlaxEditor.CustomEditors.Dedicated return; } + // Special case for reverting removed Actors + if (editor is RemovedActorDummy removedActor) + { + Editor.Log("Reverting removed actor changes to prefab (adding it)"); + + var parentActor = removedActor.ParentActor; + var restored = parentActor.AddChild(removedActor.PrefabObject.GetType()); + var prefabId = parentActor.PrefabID; + var prefabObjectId = removedActor.PrefabObject.PrefabObjectID; + string data = JsonSerializer.Serialize(removedActor.PrefabObject); + JsonSerializer.Deserialize(restored, data); + Presenter.Owner.SceneContext.Spawn(restored, parentActor, removedActor.OrderInParent); + Actor.Internal_LinkPrefab(FlaxEngine.Object.GetUnmanagedPtr(restored), ref prefabId, ref prefabObjectId); + return; + } + // Special case for new Script added to actor if (editor.Values[0] is Script script && !script.HasPrefabLink) { @@ -470,8 +562,37 @@ namespace FlaxEditor.CustomEditors.Dedicated return; } + + // Special case for new Actor added to actor + if (editor.Values[0] is Actor a && !a.HasPrefabLink) + { + Editor.Log("Reverting added actor changes to prefab (removing it)"); - editor.RevertToReferenceValue(); + // TODO: Keep previous selection. + var context = Presenter.Owner.SceneContext; + context.Select(SceneGraph.SceneGraphFactory.FindNode(a.ID)); + context.DeleteSelection(); + + return; + } + + if (Presenter.Undo != null && Presenter.Undo.Enabled) + { + var thisActor = (Actor)Values[0]; + var rootActor = thisActor.IsPrefabRoot ? thisActor : thisActor.GetPrefabRoot(); + var prefabObjects = new List(); + GetAllPrefabObjects(prefabObjects, rootActor); + using (new UndoMultiBlock(Presenter.Undo, prefabObjects, "Revert to Prefab")) + { + editor.RevertToReferenceValue(); + editor.RefreshInternal(); + } + } + else + { + editor.RevertToReferenceValue(); + editor.RefreshInternal(); + } } } } diff --git a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs index cfc11d5a5..a0ee8d3dc 100644 --- a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs +++ b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs @@ -70,7 +70,9 @@ namespace FlaxEditor.CustomEditors.Editors menu.ItemsContainer.RemoveChildren(); menu.AddButton("Copy", linkedEditor.Copy); - var b = menu.AddButton("Paste", linkedEditor.Paste); + var b = menu.AddButton("Duplicate", () => Editor.Duplicate(Index)); + b.Enabled = linkedEditor.CanPaste && !Editor._readOnly; + b = menu.AddButton("Paste", linkedEditor.Paste); b.Enabled = linkedEditor.CanPaste && !Editor._readOnly; menu.AddSeparator(); @@ -404,8 +406,10 @@ namespace FlaxEditor.CustomEditors.Editors var menu = new ContextMenu(); menu.AddButton("Copy", linkedEditor.Copy); + var b = menu.AddButton("Duplicate", () => Editor.Duplicate(Index)); + b.Enabled = linkedEditor.CanPaste && !Editor._readOnly; var paste = menu.AddButton("Paste", linkedEditor.Paste); - paste.Enabled = linkedEditor.CanPaste; + paste.Enabled = linkedEditor.CanPaste && !Editor._readOnly; if (_canReorder) { @@ -741,6 +745,34 @@ namespace FlaxEditor.CustomEditors.Editors cloned[srcIndex] = tmp; SetValue(cloned); } + + /// + /// Duplicates the list item. + /// + /// The index to duplicate. + public void Duplicate(int index) + { + if (IsSetBlocked) + return; + + var count = Count; + var newValues = Allocate(count + 1); + var oldValues = (IList)Values[0]; + + for (int i = 0; i <= index; i++) + { + newValues[i] = oldValues[i]; + } + + newValues[index + 1] = Utilities.Utils.CloneValue(oldValues[index]); + + for (int i = index + 1; i < count; i++) + { + newValues[i + 1] = oldValues[i]; + } + + SetValue(newValues); + } /// /// Shifts the specified item at the given index and moves it through the list to the other item. It supports undo. diff --git a/Source/Editor/Modules/PrefabsModule.cs b/Source/Editor/Modules/PrefabsModule.cs index 617202062..f7b984056 100644 --- a/Source/Editor/Modules/PrefabsModule.cs +++ b/Source/Editor/Modules/PrefabsModule.cs @@ -254,18 +254,29 @@ namespace FlaxEditor.Modules PrefabApplying?.Invoke(prefab, instance); // When applying changes to prefab from actor in level ignore it's root transformation (see ActorEditor.ProcessDiff) + Actor prefabRoot = null; var originalTransform = instance.LocalTransform; var originalName = instance.Name; - if (instance.IsPrefabRoot && instance.HasScene) + if (instance.HasScene) { - instance.LocalTransform = prefab.GetDefaultInstance().Transform; - instance.Name = prefab.GetDefaultInstance().Name; + prefabRoot = instance.GetPrefabRoot(); + if (prefabRoot != null && prefabRoot.IsPrefabRoot && instance.HasScene) + { + var defaultInstance = prefab.GetDefaultInstance(); + originalTransform = prefabRoot.LocalTransform; + originalName = prefabRoot.Name; + prefabRoot.LocalTransform = defaultInstance.Transform; + prefabRoot.Name = defaultInstance.Name; + } } // Call backend var failed = PrefabManager.Internal_ApplyAll(FlaxEngine.Object.GetUnmanagedPtr(instance)); - instance.LocalTransform = originalTransform; - instance.Name = originalName; + if (prefabRoot != null) + { + prefabRoot.LocalTransform = originalTransform; + prefabRoot.Name = originalName; + } if (failed) throw new Exception("Failed to apply the prefab. See log to learn more."); diff --git a/Source/Editor/Modules/SceneModule.cs b/Source/Editor/Modules/SceneModule.cs index 9ca92ddce..ff6e75e0d 100644 --- a/Source/Editor/Modules/SceneModule.cs +++ b/Source/Editor/Modules/SceneModule.cs @@ -569,6 +569,10 @@ namespace FlaxEditor.Modules return; } + // Skip if already added + if (SceneGraphFactory.Nodes.ContainsKey(actor.ID)) + return; + var node = SceneGraphFactory.BuildActorNode(actor); if (node != null) { diff --git a/Source/Editor/SceneGraph/Actors/StaticModelNode.cs b/Source/Editor/SceneGraph/Actors/StaticModelNode.cs index a7a3ed9b3..e95364c2d 100644 --- a/Source/Editor/SceneGraph/Actors/StaticModelNode.cs +++ b/Source/Editor/SceneGraph/Actors/StaticModelNode.cs @@ -141,6 +141,8 @@ namespace FlaxEditor.SceneGraph.Actors b.TooltipText = "Add a box collider to every selected model that will auto resize based on the model bounds."; b = menu.ContextMenu.AddButton("Sphere", () => OnAddCollider(window, CreateSphere)); b.TooltipText = "Add a sphere collider to every selected model that will auto resize based on the model bounds."; + b = menu.ContextMenu.AddButton("Capsule", () => OnAddCollider(window, CreateCapsule)); + b.TooltipText = "Add a capsule collider to every selected model that will auto resize based on the model bounds."; b = menu.ContextMenu.AddButton("Convex", () => OnAddCollider(window, CreateConvex)); b.TooltipText = "Generate and add a convex collider for every selected model."; b = menu.ContextMenu.AddButton("Triangle Mesh", () => OnAddCollider(window, CreateTriangle)); @@ -267,6 +269,20 @@ namespace FlaxEditor.SceneGraph.Actors spawner(collider); } + private void CreateCapsule(StaticModel actor, Spawner spawner, bool singleNode) + { + var collider = new CapsuleCollider + { + Transform = actor.Transform, + Position = actor.Box.Center, + + // Size the capsule to best fit the actor + Radius = (float)actor.Sphere.Radius / Mathf.Max((float)actor.Scale.MaxValue, 0.0001f) * 0.707f, + Height = 100f, + }; + spawner(collider); + } + private void CreateConvex(StaticModel actor, Spawner spawner, bool singleNode) { CreateMeshCollider(actor, spawner, singleNode, CollisionDataType.ConvexMesh); diff --git a/Source/Editor/SceneGraph/ISceneEditingContext.cs b/Source/Editor/SceneGraph/ISceneEditingContext.cs index d480ceba5..cc750d854 100644 --- a/Source/Editor/SceneGraph/ISceneEditingContext.cs +++ b/Source/Editor/SceneGraph/ISceneEditingContext.cs @@ -1,8 +1,9 @@ // Copyright (c) Wojciech Figat. All rights reserved. -using System.Collections.Generic; using FlaxEditor.SceneGraph; using FlaxEditor.Viewport; +using FlaxEngine; +using System.Collections.Generic; namespace FlaxEditor { @@ -39,9 +40,23 @@ namespace FlaxEditor /// void RenameSelection(); + /// + /// Deletes selected objects. + /// + void DeleteSelection(); + /// /// Focuses selected objects. /// void FocusSelection(); + + /// + /// Spawns the specified actor to the game (with undo). + /// + /// The actor. + /// The parent actor. Set null as default. + /// The order under the parent to put the spawned actor. + /// True if automatically select the spawned actor, otherwise false. + void Spawn(Actor actor, Actor parent = null, int orderInParent = -1, bool autoSelect = true); } } diff --git a/Source/Editor/SceneGraph/RootNode.cs b/Source/Editor/SceneGraph/RootNode.cs index 30e469657..da2221981 100644 --- a/Source/Editor/SceneGraph/RootNode.cs +++ b/Source/Editor/SceneGraph/RootNode.cs @@ -181,7 +181,7 @@ namespace FlaxEditor.SceneGraph public List Selection => SceneContext.Selection; /// - /// Gets the list of selected scene graph nodes in the editor context. + /// Gets the scene editing context. /// public abstract ISceneEditingContext SceneContext { get; } } diff --git a/Source/Editor/Utilities/MemberComparison.cs b/Source/Editor/Utilities/MemberComparison.cs index 3eee029d2..e8d02bece 100644 --- a/Source/Editor/Utilities/MemberComparison.cs +++ b/Source/Editor/Utilities/MemberComparison.cs @@ -61,6 +61,7 @@ namespace FlaxEditor.Utilities /// The value. public void SetMemberValue(object instance, object value) { + var originalInstance = instance; var finalMember = MemberPath.GetLastMember(ref instance); var type = finalMember.Type; @@ -92,6 +93,12 @@ namespace FlaxEditor.Utilities } finalMember.SetValue(instance, value); + + if (instance != originalInstance && finalMember.Index != null) + { + // Set collection back to the parent object (in case of properties that always return a new object like 'Spline.SplineKeyframes') + finalMember.Member.SetValue(originalInstance, instance); + } } /// diff --git a/Source/Editor/Viewport/PrefabWindowViewport.cs b/Source/Editor/Viewport/PrefabWindowViewport.cs index 3968f0379..b1b86008d 100644 --- a/Source/Editor/Viewport/PrefabWindowViewport.cs +++ b/Source/Editor/Viewport/PrefabWindowViewport.cs @@ -668,10 +668,7 @@ namespace FlaxEditor.Viewport if ((view.Flags & ViewFlags.PhysicsDebug) != 0 || view.Mode == ViewMode.PhysicsColliders) { foreach (var actor in _debugDrawActors) - { - if (actor is Collider c && c.IsActiveInHierarchy) - DebugDraw.DrawColliderDebugPhysics(c, renderContext.View); - } + DebugDraw.DrawDebugPhysics(actor, renderContext.View); } // Draw lights debug diff --git a/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs b/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs index 2b86664f2..03ba26fc8 100644 --- a/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs +++ b/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs @@ -641,5 +641,8 @@ namespace FlaxEditor.Windows.Assets /// public bool LockSelection { get; set; } + + /// + public ISceneEditingContext SceneContext => null; } } diff --git a/Source/Editor/Windows/Assets/CollisionDataWindow.cs b/Source/Editor/Windows/Assets/CollisionDataWindow.cs index 3f265eea9..fc8a5659d 100644 --- a/Source/Editor/Windows/Assets/CollisionDataWindow.cs +++ b/Source/Editor/Windows/Assets/CollisionDataWindow.cs @@ -7,6 +7,7 @@ using FlaxEditor.Content.Create; using FlaxEditor.CustomEditors; using FlaxEditor.CustomEditors.Editors; using FlaxEditor.CustomEditors.Elements; +using FlaxEditor.GUI; using FlaxEditor.Viewport.Cameras; using FlaxEditor.Viewport.Previews; using FlaxEngine; @@ -171,13 +172,49 @@ namespace FlaxEditor.Windows.Assets } private readonly SplitPanel _split; - private readonly ModelBasePreview _preview; + private readonly CollisionDataPreview _preview; private readonly CustomEditorPresenter _propertiesPresenter; private readonly PropertiesProxy _properties; private Model _collisionWiresModel; private StaticModel _collisionWiresShowActor; private bool _updateWireMesh; + private class CollisionDataPreview : ModelBasePreview + { + public bool ShowCollisionData = false; + private int _verticesCount = 0; + private int _trianglesCount = 0; + + public void SetVerticesAndTriangleCount(int verticesCount, int triangleCount) + { + _verticesCount = verticesCount; + _trianglesCount = triangleCount; + } + + /// + public CollisionDataPreview(bool useWidgets) + : base(useWidgets) + { + ViewportCamera = new FPSCamera(); + Task.ViewFlags &= ~ViewFlags.Sky & ~ViewFlags.Bloom & ~ViewFlags.EyeAdaptation; + } + + /// + public override void Draw() + { + base.Draw(); + + if (ShowCollisionData) + { + var text = string.Format("\nTriangles: {0:N0}\nVertices: {1:N0}\nMemory Size: {2}", _trianglesCount, _verticesCount, Utilities.Utils.FormatBytesCount(Asset.MemoryUsage)); + var font = Style.Current.FontMedium; + var pos = new Float2(10, 50); + Render2D.DrawText(font, text, new Rectangle(pos + Float2.One, Size), Color.Black); + Render2D.DrawText(font, text, new Rectangle(pos, Size), Color.White); + } + } + } + /// public CollisionDataWindow(Editor editor, AssetItem item) : base(editor, item) @@ -185,6 +222,12 @@ namespace FlaxEditor.Windows.Assets // Toolstrip _toolstrip.AddSeparator(); _toolstrip.AddButton(editor.Icons.CenterView64, () => _preview.ResetCamera()).LinkTooltip("Show whole collision"); + var infoButton = (ToolStripButton)_toolstrip.AddButton(editor.Icons.Info64).LinkTooltip("Show Collision Data"); + infoButton.Clicked += () => + { + _preview.ShowCollisionData = !_preview.ShowCollisionData; + infoButton.Checked = _preview.ShowCollisionData; + }; _toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/physics/colliders/collision-data.html")).LinkTooltip("See documentation to learn more"); // Split Panel @@ -197,12 +240,10 @@ namespace FlaxEditor.Windows.Assets }; // Model preview - _preview = new ModelBasePreview(true) + _preview = new CollisionDataPreview(true) { - ViewportCamera = new FPSCamera(), Parent = _split.Panel1 }; - _preview.Task.ViewFlags &= ~ViewFlags.Sky & ~ViewFlags.Bloom & ~ViewFlags.EyeAdaptation; // Asset properties _propertiesPresenter = new CustomEditorPresenter(null); @@ -240,7 +281,7 @@ namespace FlaxEditor.Windows.Assets _collisionWiresModel = FlaxEngine.Content.CreateVirtualAsset(); _collisionWiresModel.SetupLODs(new[] { 1 }); } - Editor.Internal_GetCollisionWires(FlaxEngine.Object.GetUnmanagedPtr(Asset), out var triangles, out var indices, out var _, out var _); + Editor.Internal_GetCollisionWires(FlaxEngine.Object.GetUnmanagedPtr(Asset), out var triangles, out var indices, out var triangleCount, out var indicesCount); if (triangles != null && indices != null) _collisionWiresModel.LODs[0].Meshes[0].UpdateMesh(triangles, indices); else @@ -252,6 +293,7 @@ namespace FlaxEditor.Windows.Assets } _collisionWiresShowActor.Model = _collisionWiresModel; _collisionWiresShowActor.SetMaterial(0, FlaxEngine.Content.LoadAsyncInternal(EditorAssets.WiresDebugMaterial)); + _preview.SetVerticesAndTriangleCount(triangleCount, indicesCount / 3); _preview.Asset = FlaxEngine.Content.LoadAsync(_asset.Options.Model); } diff --git a/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs b/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs index 24854afb9..0eecc4861 100644 --- a/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs +++ b/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs @@ -430,6 +430,12 @@ namespace FlaxEditor.Windows.Assets } } + /// + public void DeleteSelection() + { + Delete(); + } + /// public void FocusSelection() { @@ -488,7 +494,8 @@ namespace FlaxEditor.Windows.Assets /// The actor. /// The parent. /// The order of the actor under the parent. - public void Spawn(Actor actor, Actor parent, int orderInParent = -1) + /// True if automatically select the spawned actor, otherwise false. + public void Spawn(Actor actor, Actor parent, int orderInParent = -1, bool autoSelect = true) { if (actor == null) throw new ArgumentNullException(nameof(actor)); @@ -514,8 +521,11 @@ namespace FlaxEditor.Windows.Assets // Create undo action var action = new CustomDeleteActorsAction(new List(1) { actorNode }, true); Undo.AddAction(action); - Focus(); - Select(actorNode); + if (autoSelect) + { + Focus(); + Select(actorNode); + } } private void OnTreeRightClick(TreeNode node, Float2 location) diff --git a/Source/Editor/Windows/Assets/PrefabWindow.cs b/Source/Editor/Windows/Assets/PrefabWindow.cs index 44c21d863..7f17053ac 100644 --- a/Source/Editor/Windows/Assets/PrefabWindow.cs +++ b/Source/Editor/Windows/Assets/PrefabWindow.cs @@ -91,6 +91,9 @@ namespace FlaxEditor.Windows.Assets } } + /// + public ISceneEditingContext SceneContext => this; + /// /// Gets or sets a value indicating whether use live reloading for the prefab changes (applies prefab changes on modification by auto). /// diff --git a/Source/Editor/Windows/PropertiesWindow.cs b/Source/Editor/Windows/PropertiesWindow.cs index e90003038..03316a221 100644 --- a/Source/Editor/Windows/PropertiesWindow.cs +++ b/Source/Editor/Windows/PropertiesWindow.cs @@ -58,6 +58,9 @@ namespace FlaxEditor.Windows } } + /// + public ISceneEditingContext SceneContext => Editor.Windows.EditWin; + /// /// Initializes a new instance of the class. /// diff --git a/Source/Editor/Windows/SceneEditorWindow.cs b/Source/Editor/Windows/SceneEditorWindow.cs index e3c72d069..888cb0888 100644 --- a/Source/Editor/Windows/SceneEditorWindow.cs +++ b/Source/Editor/Windows/SceneEditorWindow.cs @@ -26,12 +26,24 @@ namespace FlaxEditor.Windows FlaxEditor.Utilities.Utils.SetupCommonInputActions(this); } + /// + public void DeleteSelection() + { + Editor.SceneEditing.Delete(); + } + /// public void FocusSelection() { Editor.Windows.EditWin.Viewport.FocusSelection(); } + /// + public void Spawn(Actor actor, Actor parent = null, int orderInParent = -1, bool autoSelect = true) + { + Editor.SceneEditing.Spawn(actor, parent, orderInParent, autoSelect); + } + /// public EditorViewport Viewport => Editor.Windows.EditWin.Viewport; diff --git a/Source/Engine/Core/Collections/HashSet.h b/Source/Engine/Core/Collections/HashSet.h index b0fa8d7b5..d730261bb 100644 --- a/Source/Engine/Core/Collections/HashSet.h +++ b/Source/Engine/Core/Collections/HashSet.h @@ -417,7 +417,7 @@ public: template bool Add(const ItemType& item) { - Bucket* bucket = Base::OnAdd(item, false); + Bucket* bucket = Base::OnAdd(item, false, true); if (bucket) bucket->Occupy(item); return bucket != nullptr; @@ -430,7 +430,7 @@ public: /// True if element has been added to the collection, otherwise false if the element is already present. bool Add(T&& item) { - Bucket* bucket = Base::OnAdd(item, false); + Bucket* bucket = Base::OnAdd(item, false, true); if (bucket) bucket->Occupy(MoveTemp(item)); return bucket != nullptr; diff --git a/Source/Engine/Core/Collections/HashSetBase.h b/Source/Engine/Core/Collections/HashSetBase.h index 3b487227a..36fcf275d 100644 --- a/Source/Engine/Core/Collections/HashSetBase.h +++ b/Source/Engine/Core/Collections/HashSetBase.h @@ -365,7 +365,7 @@ protected: } template - BucketType* OnAdd(const KeyComparableType& key, bool checkUnique = true) + BucketType* OnAdd(const KeyComparableType& key, bool checkUnique = true, bool nullIfNonUnique = false) { // Check if need to rehash elements (prevent many deleted elements that use too much of capacity) if (_deletedCount * HASH_SET_DEFAULT_SLACK_SCALE > _size) @@ -386,6 +386,8 @@ protected: Platform::CheckFailed("That key has been already added to the collection.", __FILE__, __LINE__); return nullptr; } + if (nullIfNonUnique) + return nullptr; return &_allocation.Get()[pos.ObjectIndex]; } diff --git a/Source/Engine/Debug/DebugDraw.cpp b/Source/Engine/Debug/DebugDraw.cpp index 3e5601532..2f063e2c6 100644 --- a/Source/Engine/Debug/DebugDraw.cpp +++ b/Source/Engine/Debug/DebugDraw.cpp @@ -30,6 +30,7 @@ #include "Editor/Editor.h" #include "Engine/Level/Actors/Light.h" #include "Engine/Physics/Colliders/Collider.h" +#include "Engine/Physics/Actors/IPhysicsDebug.h" #endif // Debug draw service configuration @@ -1041,11 +1042,12 @@ void DebugDraw::DrawActorsTree(Actor* actor) #if USE_EDITOR -void DebugDraw::DrawColliderDebugPhysics(Collider* collider, RenderView& view) +void DebugDraw::DrawDebugPhysics(Actor* actor, RenderView& view) { - if (!collider) + if (!actor || !actor->IsActiveInHierarchy()) return; - collider->DrawPhysicsDebug(view); + if (auto* physicsDebug = dynamic_cast(actor)) + physicsDebug->DrawPhysicsDebug(view); } void DebugDraw::DrawLightDebug(Light* light, RenderView& view) diff --git a/Source/Engine/Debug/DebugDraw.h b/Source/Engine/Debug/DebugDraw.h index 3f4ff8aff..8e2713ac5 100644 --- a/Source/Engine/Debug/DebugDraw.h +++ b/Source/Engine/Debug/DebugDraw.h @@ -107,11 +107,11 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw #if USE_EDITOR /// - /// Draws the physics debug shapes for the given collider. Editor Only + /// Draws the physics debug shapes for the given actor. Editor Only /// - /// The collider to draw. + /// The actor to draw. /// The render view to draw in. - API_FUNCTION() static void DrawColliderDebugPhysics(Collider* collider, RenderView& view); + API_FUNCTION() static void DrawDebugPhysics(Actor* actor, RenderView& view); /// /// Draws the light debug shapes for the given light. Editor Only diff --git a/Source/Engine/Level/Prefabs/Prefab.Apply.cpp b/Source/Engine/Level/Prefabs/Prefab.Apply.cpp index 4409ae161..0bed02f0b 100644 --- a/Source/Engine/Level/Prefabs/Prefab.Apply.cpp +++ b/Source/Engine/Level/Prefabs/Prefab.Apply.cpp @@ -304,6 +304,7 @@ bool PrefabInstanceData::SynchronizePrefabInstances(PrefabInstancesData& prefabI { for (int32 instanceIndex = 0; instanceIndex < prefabInstancesData.Count(); instanceIndex++) { + PROFILE_CPU_NAMED("Instance"); auto& instance = prefabInstancesData[instanceIndex]; ISerializeModifierCacheType modifier = Cache::ISerializeModifier.Get(); Scripting::ObjectsLookupIdMapping.Set(&modifier.Value->IdsMapping); @@ -377,6 +378,10 @@ bool PrefabInstanceData::SynchronizePrefabInstances(PrefabInstancesData& prefabI sceneObjects->Add(obj); } + // Generate nested prefab instances to properly handle Ids Mapping within each nested prefab + SceneObjectsFactory::PrefabSyncData prefabSyncData(*sceneObjects.Value, instance.Data, modifier.Value); + SceneObjectsFactory::SetupPrefabInstances(context, prefabSyncData); + // Apply modifications for (int32 i = existingObjectsCount - 1; i >= 0; i--) { @@ -388,6 +393,7 @@ bool PrefabInstanceData::SynchronizePrefabInstances(PrefabInstancesData& prefabI if (prefabObjectIdToDiffData.TryGet(obj->GetPrefabObjectID(), data)) { // Apply prefab changes + context.SetupIdsMapping(obj, modifier.Value); obj->Deserialize(*(ISerializable::DeserializeStream*)data, modifier.Value); } else @@ -424,7 +430,6 @@ bool PrefabInstanceData::SynchronizePrefabInstances(PrefabInstancesData& prefabI for (int32 i = 0; i < sceneObjects->Count(); i++) { SceneObject* obj = sceneObjects->At(i); - int32 dataIndex; if (instance.PrefabInstanceIdToDataIndex.TryGet(obj->GetSceneObjectId(), dataIndex)) { @@ -440,6 +445,7 @@ bool PrefabInstanceData::SynchronizePrefabInstances(PrefabInstancesData& prefabI data.RemoveMember("ParentID"); #endif + context.SetupIdsMapping(obj, modifier.Value); obj->Deserialize(data, modifier.Value); // Preserve order in parent (values from prefab are used) @@ -527,6 +533,7 @@ bool PrefabInstanceData::SynchronizePrefabInstances(PrefabInstancesData& prefabI { if (prefabInstancesData.IsEmpty()) return false; + PROFILE_CPU(); // Fully serialize default instance scene objects (accumulate all prefab and nested prefabs changes into a single linear list of objects) rapidjson_flax::Document defaultInstanceData; @@ -926,7 +933,6 @@ bool Prefab::ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPr // TODO: what if user applied prefab with references to the other objects from scene? clear them or what? JsonTools::ChangeIds(diffDataDocument, objectInstanceIdToPrefabObjectId); } - dataBuffer.Clear(); CollectionPoolCache::ScopeCache sceneObjects = ActorsCache::SceneObjectsListCache.Get(); // Destroy default instance and some cache data in Prefab @@ -1002,6 +1008,32 @@ bool Prefab::ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPr obj->RegisterObject(); } + // Generate nested prefab instances to properly handle Ids Mapping within each nested prefab + rapidjson_flax::Document targetDataDocument; + if (NestedPrefabs.HasItems()) + { + targetDataDocument.Parse(dataBuffer.GetString(), dataBuffer.GetSize()); + SceneObjectsFactory::PrefabSyncData prefabSyncData(*sceneObjects.Value, targetDataDocument, modifier.Value); + SceneObjectsFactory::SetupPrefabInstances(context, prefabSyncData); + + if (context.Instances.HasItems()) + { + // Only main prefab instance is allowed (in case nested prefab was added to this prefab) + for (auto i = context.ObjectToInstance.Begin(); i.IsNotEnd(); ++i) + { + if (i->Value != 0) + context.ObjectToInstance.Remove(i); + } + context.Instances.Resize(1); + + // Trash object mapping to prevent messing up prefab structure when applying hierarchy changes (only nested instances are used) + context.Instances[0].IdsMapping.Clear(); + } + } + + dataBuffer.Clear(); + auto originalIdsMapping = modifier.Value->IdsMapping; + // Deserialize prefab objects and apply modifications for (int32 i = 0; i < ObjectsCount; i++) { @@ -1037,6 +1069,9 @@ bool Prefab::ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPr } } + // Use the initial Ids Mapping (SetupIdsMapping overrides it for instanced prefabs) + modifier.Value->IdsMapping = originalIdsMapping; + // Deserialize new prefab objects newPrefabInstanceIdToDataIndexCounter = 0; for (auto i = newPrefabInstanceIdToDataIndex.Begin(); i.IsNotEnd(); ++i) @@ -1283,6 +1318,10 @@ bool Prefab::UpdateInternal(const Array& defaultInstanceObjects, r ObjectsDataCache.Add(objectId, &objData); ObjectsCount++; + Guid parentID; + if (JsonTools::GetGuidIfValid(parentID, objData, "ParentID")) + ObjectsHierarchyCache[parentID].Add(objectId); + Guid prefabId = JsonTools::GetGuid(objData, "PrefabID"); if (prefabId.IsValid() && !NestedPrefabs.Contains(prefabId)) { diff --git a/Source/Engine/Level/Prefabs/PrefabManager.cpp b/Source/Engine/Level/Prefabs/PrefabManager.cpp index d1ee99721..a0b4bb807 100644 --- a/Source/Engine/Level/Prefabs/PrefabManager.cpp +++ b/Source/Engine/Level/Prefabs/PrefabManager.cpp @@ -172,7 +172,9 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const SpawnOptions& options) SceneObjectsFactory::HandleObjectDeserializationError(stream); } SceneObjectsFactory::PrefabSyncData prefabSyncData(*sceneObjects.Value, data, modifier.Value); - if (options.WithSync) + bool withSync = options.WithSync || prefab->NestedPrefabs.HasItems(); // Nested prefabs needs prefab instances generation for correct IdsMapping if the same prefab exists multiple times + // TODO: let prefab check if has multiple nested prefabs at cook time? + if (withSync) { // Synchronize new prefab instances (prefab may have new objects added so deserialized instances need to synchronize with it) // TODO: resave and force sync prefabs during game cooking so this step could be skipped in game @@ -187,14 +189,14 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const SpawnOptions& options) if (obj) SceneObjectsFactory::Deserialize(context, obj, stream); } - Scripting::ObjectsLookupIdMapping.Set(prevIdMapping); // Synchronize prefab instances (prefab may have new objects added or some removed so deserialized instances need to synchronize with it) - if (options.WithSync) + if (withSync) { // TODO: resave and force sync scenes during game cooking so this step could be skipped in game SceneObjectsFactory::SynchronizePrefabInstances(context, prefabSyncData); } + Scripting::ObjectsLookupIdMapping.Set(prevIdMapping); // Pick prefab root object Actor* root = nullptr; diff --git a/Source/Engine/Level/Scene/SceneRendering.cpp b/Source/Engine/Level/Scene/SceneRendering.cpp index 01ac689dc..1fffa79c8 100644 --- a/Source/Engine/Level/Scene/SceneRendering.cpp +++ b/Source/Engine/Level/Scene/SceneRendering.cpp @@ -7,6 +7,7 @@ #include "Engine/Graphics/RenderView.h" #include "Engine/Renderer/RenderList.h" #include "Engine/Threading/JobSystem.h" +#include "Engine/Physics/Actors/IPhysicsDebug.h" #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Profiler/ProfilerMemory.h" #if !BUILD_RELEASE @@ -107,10 +108,10 @@ void SceneRendering::Draw(RenderContextBatch& renderContextBatch, DrawCategory c if (EnumHasAnyFlags(view.Flags, ViewFlags::PhysicsDebug) || view.Mode == ViewMode::PhysicsColliders) { PROFILE_CPU_NAMED("PhysicsDebug"); - const PhysicsDebugCallback* physicsDebugData = PhysicsDebug.Get(); + const auto* physicsDebugData = PhysicsDebug.Get(); for (int32 i = 0; i < PhysicsDebug.Count(); i++) { - physicsDebugData[i](view); + physicsDebugData[i]->DrawPhysicsDebug(view); } } diff --git a/Source/Engine/Level/Scene/SceneRendering.h b/Source/Engine/Level/Scene/SceneRendering.h index 59f997f6b..293927ea8 100644 --- a/Source/Engine/Level/Scene/SceneRendering.h +++ b/Source/Engine/Level/Scene/SceneRendering.h @@ -11,6 +11,7 @@ class SceneRenderTask; class SceneRendering; +class IPhysicsDebug; struct PostProcessSettings; struct RenderContext; struct RenderContextBatch; @@ -74,7 +75,6 @@ public: class FLAXENGINE_API SceneRendering { #if USE_EDITOR - typedef Function PhysicsDebugCallback; typedef Function LightsDebugCallback; friend class ViewportIconsRendererService; #endif @@ -106,7 +106,7 @@ public: private: #if USE_EDITOR - Array PhysicsDebug; + Array PhysicsDebug; Array LightsDebug; Array ViewportIcons; #endif @@ -150,20 +150,14 @@ public: } #if USE_EDITOR - template - FORCE_INLINE void AddPhysicsDebug(T* obj) + FORCE_INLINE void AddPhysicsDebug(IPhysicsDebug* obj) { - PhysicsDebugCallback f; - f.Bind(obj); - PhysicsDebug.Add(f); + PhysicsDebug.Add(obj); } - template - void RemovePhysicsDebug(T* obj) + FORCE_INLINE void RemovePhysicsDebug(IPhysicsDebug* obj) { - PhysicsDebugCallback f; - f.Bind(obj); - PhysicsDebug.Remove(f); + PhysicsDebug.Remove(obj); } template diff --git a/Source/Engine/Level/SceneObjectsFactory.cpp b/Source/Engine/Level/SceneObjectsFactory.cpp index 919c0eed2..576b95b46 100644 --- a/Source/Engine/Level/SceneObjectsFactory.cpp +++ b/Source/Engine/Level/SceneObjectsFactory.cpp @@ -101,13 +101,25 @@ ISerializeModifier* SceneObjectsFactory::Context::GetModifier() void SceneObjectsFactory::Context::SetupIdsMapping(const SceneObject* obj, ISerializeModifier* modifier) const { int32 instanceIndex; - if (ObjectToInstance.TryGet(obj->GetID(), instanceIndex) && instanceIndex != modifier->CurrentInstance) + const Guid id = obj->GetID(); + if (ObjectToInstance.TryGet(id, instanceIndex)) { // Apply the current prefab instance objects ids table to resolve references inside a prefab properly - modifier->CurrentInstance = instanceIndex; const auto& instance = Instances[instanceIndex]; - for (const auto& e : instance.IdsMapping) - modifier->IdsMapping[e.Key] = e.Value; + if (instanceIndex != modifier->CurrentInstance) + { + modifier->CurrentInstance = instanceIndex; + for (const auto& e : instance.IdsMapping) + modifier->IdsMapping[e.Key] = e.Value; + } + int32 nestedIndex; + if (instance.ObjectToNested.TryGet(id, nestedIndex)) + { + // Each nested prefab has own object ids mapping that takes precedence + const auto& nested = instance.Nested[nestedIndex]; + for (const auto& e : nested.IdsMapping) + modifier->IdsMapping[e.Key] = e.Value; + } } } @@ -499,6 +511,34 @@ void SceneObjectsFactory::SetupPrefabInstances(Context& context, const PrefabSyn prefab = Content::LoadAsync(prefabId); if (prefab && !prefab->WaitForLoaded()) { + // If prefab instance contains multiple nested prefabs, then need to build separate Ids remapping for each of them to prevent collisions + int32 nestedIndex = -1; + if (prefabInstance.Nested.HasItems()) + { + // Check only the last instance if the current object belongs to it + for (int32 j = prefabInstance.Nested.Count() - 1; j >= 0; j--) + { + const auto& e = prefabInstance.Nested[j]; + if (e.Prefab == prefab && e.RootObjectId != prefabObjectId) + { + nestedIndex = j; + break; + } + } + } + if (nestedIndex == -1) + { + nestedIndex = prefabInstance.Nested.Count(); + auto& e = prefabInstance.Nested.AddOne(); + e.Prefab = prefab; + e.RootObjectId = prefabObjectId; + } + + // Map this object into this instance to inherit all objects from it when looking up via IdsMapping + prefabInstance.ObjectToNested[id] = nestedIndex; + auto& nestedInstance = prefabInstance.Nested[nestedIndex]; + nestedInstance.IdsMapping[prefabObjectId] = id; + // Map prefab object ID to the deserialized instance ID prefabInstance.IdsMapping[prefabObjectId] = id; goto NESTED_PREFAB_WALK; @@ -794,23 +834,20 @@ void SceneObjectsFactory::SynchronizeNewPrefabInstances(Context& context, Prefab if (spawned) continue; - // Map prefab object ID to this actor's prefab instance so the new objects gets added to it + // Map prefab object ID to this actor's prefab instance so the new objects get added to it context.SetupIdsMapping(actor, data.Modifier); data.Modifier->IdsMapping[actorPrefabObjectId] = actorId; Scripting::ObjectsLookupIdMapping.Set(&data.Modifier->IdsMapping); // Create instance (including all children) - SynchronizeNewPrefabInstance(context, data, prefab, actor, prefabObjectId); + SynchronizeNewPrefabInstance(context, data, prefab, actor, prefabObjectId, actor->GetID()); } } -void SceneObjectsFactory::SynchronizeNewPrefabInstance(Context& context, PrefabSyncData& data, Prefab* prefab, Actor* actor, const Guid& prefabObjectId) +void SceneObjectsFactory::SynchronizeNewPrefabInstance(Context& context, PrefabSyncData& data, Prefab* prefab, Actor* actor, const Guid& prefabObjectId, const Guid& nestedInstanceId) { PROFILE_CPU_NAMED("SynchronizeNewPrefabInstance"); - // Missing object found! - LOG(Info, "Actor {0} has missing child object (PrefabObjectID: {1}, PrefabID: {2}, Path: {3})", actor->ToString(), prefabObjectId, prefab->GetID(), prefab->GetPath()); - // Get prefab object data from the prefab const ISerializable::DeserializeStream* prefabData; if (!prefab->ObjectsDataCache.TryGet(prefabObjectId, prefabData)) @@ -830,6 +867,7 @@ void SceneObjectsFactory::SynchronizeNewPrefabInstance(Context& context, PrefabS LOG(Warning, "Failed to create object {1} from prefab {0}.", prefab->ToString(), prefabObjectId); return; } + LOG(Info, "Actor {0} has missing child object '{4}' (PrefabObjectID: {1}, PrefabID: {2}, Path: {3})", actor->ToString(), prefabObjectId, prefab->GetID(), prefab->GetPath(), child->GetType().ToString()); // Register object child->RegisterObject(); @@ -846,17 +884,51 @@ void SceneObjectsFactory::SynchronizeNewPrefabInstance(Context& context, PrefabS context.ObjectToInstance[id] = instanceIndex; auto& prefabInstance = context.Instances[instanceIndex]; prefabInstance.IdsMapping[prefabObjectId] = id; + + // Check if it's a nested prefab + const ISerializable::DeserializeStream* nestedPrefabData; + Guid nestedPrefabObjectId; + if (prefab->ObjectsDataCache.TryGet(prefabObjectId, nestedPrefabData) && JsonTools::GetGuidIfValid(nestedPrefabObjectId, *nestedPrefabData, "PrefabObjectID")) + { + // Try reusing parent nested instance (or make a new one) + int32 nestedIndex = -1; + if (!prefabInstance.ObjectToNested.TryGet(nestedInstanceId, nestedIndex)) + { + if (auto* nestedPrefab = Content::LoadAsync(JsonTools::GetGuid(*prefabData, "PrefabID"))) + { + if (prefabInstance.Nested.HasItems()) + { + // Check only the last instance if the current object belongs to it + const auto& e = prefabInstance.Nested.Last(); + if (e.Prefab == nestedPrefab && e.RootObjectId != nestedPrefabObjectId) + nestedIndex = prefabInstance.Nested.Count() - 1; + } + if (nestedIndex == -1) + { + nestedIndex = prefabInstance.Nested.Count(); + auto& e = prefabInstance.Nested.AddOne(); + e.Prefab = nestedPrefab; + e.RootObjectId = nestedPrefabObjectId; + } + } + } + if (nestedIndex != -1) + { + // Insert into nested instance + prefabInstance.ObjectToNested[id] = nestedIndex; + auto& nestedInstance = prefabInstance.Nested[nestedIndex]; + nestedInstance.IdsMapping[nestedPrefabObjectId] = id; + } + } } // Use loop to add even more objects to added objects (prefab can have one new object that has another child, we need to add that child) - // TODO: prefab could cache lookup object id -> children ids - for (auto q = prefab->ObjectsDataCache.Begin(); q.IsNotEnd(); ++q) + const auto* hierarchy = prefab->ObjectsHierarchyCache.TryGet(prefabObjectId); + if (hierarchy) { - Guid qParentId; - if (JsonTools::GetGuidIfValid(qParentId, *q->Value, "ParentID") && qParentId == prefabObjectId) + for (const Guid& e : *hierarchy) { - const Guid qPrefabObjectId = JsonTools::GetGuid(*q->Value, "ID"); - SynchronizeNewPrefabInstance(context, data, prefab, actor, qPrefabObjectId); + SynchronizeNewPrefabInstance(context, data, prefab, actor, e, id); } } } diff --git a/Source/Engine/Level/SceneObjectsFactory.h b/Source/Engine/Level/SceneObjectsFactory.h index 904bef696..712e05cc6 100644 --- a/Source/Engine/Level/SceneObjectsFactory.h +++ b/Source/Engine/Level/SceneObjectsFactory.h @@ -16,6 +16,13 @@ class FLAXENGINE_API SceneObjectsFactory { public: + struct NestedPrefabInstance + { + Prefab* Prefab; + Guid RootObjectId; + Dictionary IdsMapping; + }; + struct PrefabInstance { int32 StatIndex; @@ -24,6 +31,8 @@ public: Prefab* Prefab; bool FixRootParent = false; Dictionary IdsMapping; + Array Nested; + Dictionary ObjectToNested; }; struct Context @@ -136,5 +145,5 @@ public: private: static void SynchronizeNewPrefabInstances(Context& context, PrefabSyncData& data, Prefab* prefab, Actor* actor, const Guid& actorPrefabObjectId, int32 i, const ISerializable::DeserializeStream& stream); - static void SynchronizeNewPrefabInstance(Context& context, PrefabSyncData& data, Prefab* prefab, Actor* actor, const Guid& prefabObjectId); + static void SynchronizeNewPrefabInstance(Context& context, PrefabSyncData& data, Prefab* prefab, Actor* actor, const Guid& prefabObjectId, const Guid& nestedInstanceId); }; diff --git a/Source/Engine/Physics/Actors/Cloth.cpp b/Source/Engine/Physics/Actors/Cloth.cpp index a55cddd2a..1f833fa0e 100644 --- a/Source/Engine/Physics/Actors/Cloth.cpp +++ b/Source/Engine/Physics/Actors/Cloth.cpp @@ -462,7 +462,7 @@ void Cloth::OnEnable() { GetSceneRendering()->AddActor(this, _sceneRenderingKey); #if USE_EDITOR - GetSceneRendering()->AddPhysicsDebug(this); + GetSceneRendering()->AddPhysicsDebug(this); #endif #if WITH_CLOTH if (_cloth) @@ -481,7 +481,7 @@ void Cloth::OnDisable() PhysicsBackend::RemoveCloth(GetPhysicsScene()->GetPhysicsScene(), _cloth); #endif #if USE_EDITOR - GetSceneRendering()->RemovePhysicsDebug(this); + GetSceneRendering()->RemovePhysicsDebug(this); #endif GetSceneRendering()->RemoveActor(this, _sceneRenderingKey); } diff --git a/Source/Engine/Physics/Actors/Cloth.h b/Source/Engine/Physics/Actors/Cloth.h index d10c18fea..f83f1c499 100644 --- a/Source/Engine/Physics/Actors/Cloth.h +++ b/Source/Engine/Physics/Actors/Cloth.h @@ -4,6 +4,7 @@ #include "Engine/Level/Actor.h" #include "Engine/Level/Actors/ModelInstanceActor.h" +#include "IPhysicsDebug.h" // Used internally to validate cloth data against invalid nan/inf values #define USE_CLOTH_SANITY_CHECKS 0 @@ -12,6 +13,9 @@ /// Physical simulation actor for cloth objects made of vertices that are simulated as cloth particles with physical properties, forces, and constraints to affect cloth behavior. /// API_CLASS(Attributes="ActorContextMenu(\"New/Physics/Cloth\"), ActorToolbox(\"Physics\")") class FLAXENGINE_API Cloth : public Actor +#if USE_EDITOR + , public IPhysicsDebug +#endif { DECLARE_SCENE_OBJECT(Cloth); @@ -364,9 +368,7 @@ protected: void OnPhysicsSceneChanged(PhysicsScene* previous) override; private: -#if USE_EDITOR - void DrawPhysicsDebug(RenderView& view); -#endif + ImplementPhysicsDebug; bool CreateCloth(); void DestroyCloth(); void CalculateInvMasses(Array& invMasses); diff --git a/Source/Engine/Physics/Actors/IPhysicsDebug.h b/Source/Engine/Physics/Actors/IPhysicsDebug.h new file mode 100644 index 000000000..cedd3d822 --- /dev/null +++ b/Source/Engine/Physics/Actors/IPhysicsDebug.h @@ -0,0 +1,18 @@ +// Copyright (c) Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Core/Types/BaseTypes.h" + +#if USE_EDITOR +class FLAXENGINE_API IPhysicsDebug +{ +public: + virtual void DrawPhysicsDebug(struct RenderView& view) + { + } +}; +#define ImplementPhysicsDebug void DrawPhysicsDebug(RenderView& view) +#else +#define ImplementPhysicsDebug +#endif diff --git a/Source/Engine/Physics/Actors/WheeledVehicle.cpp b/Source/Engine/Physics/Actors/WheeledVehicle.cpp index 248bffc20..a4259a8c8 100644 --- a/Source/Engine/Physics/Actors/WheeledVehicle.cpp +++ b/Source/Engine/Physics/Actors/WheeledVehicle.cpp @@ -356,19 +356,19 @@ void WheeledVehicle::Setup() void WheeledVehicle::DrawPhysicsDebug(RenderView& view) { // Wheels shapes - for (const auto& data : _wheelsData) + for (const auto& wheel : _wheels) { - int32 wheelIndex = 0; - for (; wheelIndex < _wheels.Count(); wheelIndex++) - { - if (_wheels[wheelIndex].Collider == data.Collider) - break; - } - if (wheelIndex == _wheels.Count()) - break; - const auto& wheel = _wheels[wheelIndex]; if (wheel.Collider && wheel.Collider->GetParent() == this && !wheel.Collider->GetIsTrigger()) { + WheelData data = { wheel.Collider, wheel.Collider->GetLocalOrientation() }; + for (auto& e : _wheelsData) + { + if (e.Collider == data.Collider) + { + data = e; + break; + } + } const Vector3 currentPos = wheel.Collider->GetPosition(); const Vector3 basePos = currentPos - Vector3(0, data.State.SuspensionOffset, 0); const Quaternion wheelDebugOrientation = GetOrientation() * Quaternion::Euler(-data.State.RotationAngle, data.State.SteerAngle, 0) * Quaternion::Euler(90, 0, 90); @@ -387,25 +387,28 @@ void WheeledVehicle::DrawPhysicsDebug(RenderView& view) void WheeledVehicle::OnDebugDrawSelected() { // Wheels shapes - for (const auto& data : _wheelsData) + for (const auto& wheel : _wheels) { - int32 wheelIndex = 0; - for (; wheelIndex < _wheels.Count(); wheelIndex++) - { - if (_wheels[wheelIndex].Collider == data.Collider) - break; - } - if (wheelIndex == _wheels.Count()) - break; - const auto& wheel = _wheels[wheelIndex]; if (wheel.Collider && wheel.Collider->GetParent() == this && !wheel.Collider->GetIsTrigger()) { + WheelData data = { wheel.Collider, wheel.Collider->GetLocalOrientation() }; + for (auto& e : _wheelsData) + { + if (e.Collider == data.Collider) + { + data = e; + break; + } + } const Vector3 currentPos = wheel.Collider->GetPosition(); const Vector3 basePos = currentPos - Vector3(0, data.State.SuspensionOffset, 0); const Quaternion wheelDebugOrientation = GetOrientation() * Quaternion::Euler(-data.State.RotationAngle, data.State.SteerAngle, 0) * Quaternion::Euler(90, 0, 90); - Transform actorPose = Transform::Identity, shapePose = Transform::Identity; - PhysicsBackend::GetRigidActorPose(_actor, actorPose.Translation, actorPose.Orientation); - PhysicsBackend::GetShapeLocalPose(wheel.Collider->GetPhysicsShape(), shapePose.Translation, shapePose.Orientation); + Transform actorPose = GetTransform(), shapePose = wheel.Collider->GetLocalTransform(); + actorPose.Scale = Float3::One; + if (_actor) + PhysicsBackend::GetRigidActorPose(_actor, actorPose.Translation, actorPose.Orientation); + if (wheel.Collider->GetPhysicsShape()) + PhysicsBackend::GetShapeLocalPose(wheel.Collider->GetPhysicsShape(), shapePose.Translation, shapePose.Orientation); DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(basePos, wheel.Radius * 0.07f), Color::Blue * 0.3f, 0, false); DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(currentPos, wheel.Radius * 0.08f), Color::Blue * 0.8f, 0, false); DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(actorPose.LocalToWorld(shapePose.Translation), wheel.Radius * 0.11f), Color::OrangeRed * 0.8f, 0, false); @@ -561,14 +564,14 @@ void WheeledVehicle::BeginPlay(SceneBeginData* data) #endif #if USE_EDITOR - GetSceneRendering()->AddPhysicsDebug(this); + GetSceneRendering()->AddPhysicsDebug(this); #endif } void WheeledVehicle::EndPlay() { #if USE_EDITOR - GetSceneRendering()->RemovePhysicsDebug(this); + GetSceneRendering()->RemovePhysicsDebug(this); #endif #if WITH_VEHICLE diff --git a/Source/Engine/Physics/Actors/WheeledVehicle.h b/Source/Engine/Physics/Actors/WheeledVehicle.h index 87424fe72..916a9f9ee 100644 --- a/Source/Engine/Physics/Actors/WheeledVehicle.h +++ b/Source/Engine/Physics/Actors/WheeledVehicle.h @@ -5,12 +5,16 @@ #include "Engine/Physics/Actors/RigidBody.h" #include "Engine/Physics/Colliders/Collider.h" #include "Engine/Scripting/ScriptingObjectReference.h" +#include "IPhysicsDebug.h" /// /// Representation of the car vehicle that uses wheels. Built on top of the RigidBody with collider representing its chassis shape and wheels. /// /// API_CLASS(Attributes="ActorContextMenu(\"New/Physics/Wheeled Vehicle\"), ActorToolbox(\"Physics\")") class FLAXENGINE_API WheeledVehicle : public RigidBody +#if USE_EDITOR + , public IPhysicsDebug +#endif { friend class PhysicsBackend; friend struct ScenePhysX; @@ -644,13 +648,9 @@ public: /// API_FUNCTION() void Setup(); -private: -#if USE_EDITOR - void DrawPhysicsDebug(RenderView& view); -#endif - public: // [Vehicle] + ImplementPhysicsDebug; #if USE_EDITOR void OnDebugDrawSelected() override; #endif diff --git a/Source/Engine/Physics/Colliders/BoxCollider.h b/Source/Engine/Physics/Colliders/BoxCollider.h index 9b9b964a1..c0eeaea5c 100644 --- a/Source/Engine/Physics/Colliders/BoxCollider.h +++ b/Source/Engine/Physics/Colliders/BoxCollider.h @@ -58,9 +58,7 @@ public: protected: // [Collider] + ImplementPhysicsDebug; void UpdateBounds() override; void GetGeometry(CollisionShape& collision) override; -#if USE_EDITOR - void DrawPhysicsDebug(RenderView& view) override; -#endif }; diff --git a/Source/Engine/Physics/Colliders/CapsuleCollider.h b/Source/Engine/Physics/Colliders/CapsuleCollider.h index 9275b99e1..234916f87 100644 --- a/Source/Engine/Physics/Colliders/CapsuleCollider.h +++ b/Source/Engine/Physics/Colliders/CapsuleCollider.h @@ -62,9 +62,7 @@ public: protected: // [Collider] + ImplementPhysicsDebug; void UpdateBounds() override; void GetGeometry(CollisionShape& collision) override; -#if USE_EDITOR - void DrawPhysicsDebug(RenderView& view) override; -#endif }; diff --git a/Source/Engine/Physics/Colliders/CharacterController.h b/Source/Engine/Physics/Colliders/CharacterController.h index 918f5beb9..367b87df1 100644 --- a/Source/Engine/Physics/Colliders/CharacterController.h +++ b/Source/Engine/Physics/Colliders/CharacterController.h @@ -267,13 +267,11 @@ public: protected: // [PhysicsActor] + ImplementPhysicsDebug; void UpdateGeometry() override; void GetGeometry(CollisionShape& collision) override; void BeginPlay(SceneBeginData* data) override; void EndPlay() override; -#if USE_EDITOR - void DrawPhysicsDebug(RenderView& view) override; -#endif void OnActiveInTreeChanged() override; void OnEnable() override; void OnDisable() override; diff --git a/Source/Engine/Physics/Colliders/Collider.cpp b/Source/Engine/Physics/Colliders/Collider.cpp index 5ebef0a9f..7a5918c49 100644 --- a/Source/Engine/Physics/Colliders/Collider.cpp +++ b/Source/Engine/Physics/Colliders/Collider.cpp @@ -145,7 +145,7 @@ void Collider::OnEnable() if (EnumHasAnyFlags(_staticFlags, StaticFlags::Navigation) && !_isTrigger) GetScene()->Navigation.Actors.Add(this); #if USE_EDITOR - GetSceneRendering()->AddPhysicsDebug(this); + GetSceneRendering()->AddPhysicsDebug(this); #endif PhysicsColliderActor::OnEnable(); @@ -158,7 +158,7 @@ void Collider::OnDisable() if (EnumHasAnyFlags(_staticFlags, StaticFlags::Navigation) && !_isTrigger) GetScene()->Navigation.Actors.Remove(this); #if USE_EDITOR - GetSceneRendering()->RemovePhysicsDebug(this); + GetSceneRendering()->RemovePhysicsDebug(this); #endif } @@ -284,14 +284,6 @@ void Collider::RemoveStaticActor() _staticActor = nullptr; } -#if USE_EDITOR - -void Collider::DrawPhysicsDebug(RenderView& view) -{ -} - -#endif - void Collider::BeginPlay(SceneBeginData* data) { // Check if has no shape created (it means no rigidbody requested it but also collider may be spawned at runtime) diff --git a/Source/Engine/Physics/Colliders/Collider.h b/Source/Engine/Physics/Colliders/Collider.h index 17e1d7883..edc73b8a3 100644 --- a/Source/Engine/Physics/Colliders/Collider.h +++ b/Source/Engine/Physics/Colliders/Collider.h @@ -6,6 +6,7 @@ #include "Engine/Content/JsonAsset.h" #include "Engine/Content/JsonAssetReference.h" #include "Engine/Physics/Actors/PhysicsColliderActor.h" +#include "Engine/Physics/Actors/IPhysicsDebug.h" struct RayCastHit; class RigidBody; @@ -16,6 +17,9 @@ class RigidBody; /// /// API_CLASS(Abstract) class FLAXENGINE_API Collider : public PhysicsColliderActor, protected IAssetReference +#if USE_EDITOR + , public IPhysicsDebug +#endif { API_AUTO_SERIALIZATION(); DECLARE_SCENE_OBJECT_ABSTRACT(Collider); @@ -162,10 +166,6 @@ public: void ClosestPoint(const Vector3& point, Vector3& result) const final; bool ContainsPoint(const Vector3& point) const final; -#if USE_EDITOR - virtual void DrawPhysicsDebug(RenderView& view); -#endif - protected: // [PhysicsColliderActor] void OnEnable() override; diff --git a/Source/Engine/Physics/Colliders/MeshCollider.h b/Source/Engine/Physics/Colliders/MeshCollider.h index 89f013552..44314adff 100644 --- a/Source/Engine/Physics/Colliders/MeshCollider.h +++ b/Source/Engine/Physics/Colliders/MeshCollider.h @@ -37,9 +37,7 @@ public: protected: // [Collider] -#if USE_EDITOR - void DrawPhysicsDebug(RenderView& view) override; -#endif + ImplementPhysicsDebug; void UpdateBounds() override; void GetGeometry(CollisionShape& collision) override; void OnAssetChanged(Asset* asset, void* caller) override; diff --git a/Source/Engine/Physics/Colliders/SphereCollider.h b/Source/Engine/Physics/Colliders/SphereCollider.h index 3d9df79b3..67addf17b 100644 --- a/Source/Engine/Physics/Colliders/SphereCollider.h +++ b/Source/Engine/Physics/Colliders/SphereCollider.h @@ -42,9 +42,7 @@ public: protected: // [Collider] -#if USE_EDITOR - void DrawPhysicsDebug(RenderView& view) override; -#endif + ImplementPhysicsDebug; void UpdateBounds() override; void GetGeometry(CollisionShape& collision) override; }; diff --git a/Source/Engine/Physics/Colliders/SplineCollider.h b/Source/Engine/Physics/Colliders/SplineCollider.h index cb8f1d5ee..1e41802e5 100644 --- a/Source/Engine/Physics/Colliders/SplineCollider.h +++ b/Source/Engine/Physics/Colliders/SplineCollider.h @@ -67,9 +67,7 @@ public: protected: // [Collider] -#if USE_EDITOR - void DrawPhysicsDebug(RenderView& view) override; -#endif + ImplementPhysicsDebug; void UpdateBounds() override; void GetGeometry(CollisionShape& collision) override; }; diff --git a/Source/Engine/Physics/Joints/Joint.cpp b/Source/Engine/Physics/Joints/Joint.cpp index 950f2d146..902a58796 100644 --- a/Source/Engine/Physics/Joints/Joint.cpp +++ b/Source/Engine/Physics/Joints/Joint.cpp @@ -298,7 +298,7 @@ void Joint::EndPlay() void Joint::OnEnable() { - GetSceneRendering()->AddPhysicsDebug(this); + GetSceneRendering()->AddPhysicsDebug(this); // Base Actor::OnEnable(); @@ -306,7 +306,7 @@ void Joint::OnEnable() void Joint::OnDisable() { - GetSceneRendering()->RemovePhysicsDebug(this); + GetSceneRendering()->RemovePhysicsDebug(this); // Base Actor::OnDisable(); diff --git a/Source/Engine/Physics/Joints/Joint.h b/Source/Engine/Physics/Joints/Joint.h index 7583e449d..299a66f9e 100644 --- a/Source/Engine/Physics/Joints/Joint.h +++ b/Source/Engine/Physics/Joints/Joint.h @@ -5,6 +5,7 @@ #include "Engine/Level/Actor.h" #include "Engine/Physics/Types.h" #include "Engine/Scripting/ScriptingObjectReference.h" +#include "Engine/Physics/Actors/IPhysicsDebug.h" class IPhysicsActor; @@ -17,6 +18,9 @@ class IPhysicsActor; /// /// API_CLASS(Abstract) class FLAXENGINE_API Joint : public Actor +#if USE_EDITOR + , public IPhysicsDebug +#endif { DECLARE_SCENE_OBJECT_ABSTRACT(Joint); protected: @@ -174,9 +178,7 @@ protected: Vector3 GetTargetPosition() const; Quaternion GetTargetOrientation() const; virtual void* CreateJoint(const struct PhysicsJointDesc& desc) = 0; -#if USE_EDITOR - virtual void DrawPhysicsDebug(RenderView& view); -#endif + ImplementPhysicsDebug; private: void Delete(); diff --git a/Source/Engine/Renderer/ShadowsPass.cpp b/Source/Engine/Renderer/ShadowsPass.cpp index fc93202e8..ea26eb043 100644 --- a/Source/Engine/Renderer/ShadowsPass.cpp +++ b/Source/Engine/Renderer/ShadowsPass.cpp @@ -1366,10 +1366,11 @@ RETRY_ATLAS_SETUP: tile.LinkedRectTile = nullptr; auto& linkedTile = linkedAtlasLight->Tiles[tileIndex]; - // Check if both lights use the same projections - if (tile.WorldToShadow == linkedTile.WorldToShadow && linkedTile.RectTile) + // Link tile and use its projection + if (linkedTile.RectTile) { tile.LinkedRectTile = linkedTile.RectTile; + tile.WorldToShadow = linkedTile.WorldToShadow; } } } diff --git a/Source/Engine/Serialization/Serialization.cpp b/Source/Engine/Serialization/Serialization.cpp index 45b03a07c..a3dfc6ffa 100644 --- a/Source/Engine/Serialization/Serialization.cpp +++ b/Source/Engine/Serialization/Serialization.cpp @@ -790,14 +790,14 @@ void Serialization::Deserialize(ISerializable::DeserializeStream& stream, Matrix DESERIALIZE_HELPER(stream, "M44", v.M44, 0); } -bool Serialization::ShouldSerialize(const SceneObject* v, const SceneObject* other) +bool Serialization::ShouldSerializeRef(const SceneObject* v, const SceneObject* other) { bool result = v != other; if (result && v && other && v->HasPrefabLink() && other->HasPrefabLink()) { // Special case when saving reference to prefab object and the objects are different but the point to the same prefab object // In that case, skip saving reference as it's defined in prefab (will be populated via IdsMapping during deserialization) - result &= v->GetPrefabObjectID() != other->GetPrefabObjectID(); + result = v->GetPrefabObjectID() != other->GetPrefabObjectID(); } return result; } diff --git a/Source/Engine/Serialization/Serialization.h b/Source/Engine/Serialization/Serialization.h index b8167e8d2..d3205ad83 100644 --- a/Source/Engine/Serialization/Serialization.h +++ b/Source/Engine/Serialization/Serialization.h @@ -448,15 +448,20 @@ namespace Serialization // Scripting Object - FLAXENGINE_API bool ShouldSerialize(const SceneObject* v, const SceneObject* other); + inline bool ShouldSerializeRef(const ScriptingObject* v, const ScriptingObject* other) + { + return v != other; + } + + FLAXENGINE_API bool ShouldSerializeRef(const SceneObject* v, const SceneObject* other); template - inline typename TEnableIf::Value, bool>::Type ShouldSerialize(const T*& v, const void* otherObj) + inline typename TEnableIf, TNot>>::Value, bool>::Type ShouldSerialize(const T* v, const void* otherObj) { return !otherObj || v != *(const T**)otherObj; } template - inline typename TEnableIf::Value>::Type Serialize(ISerializable::SerializeStream& stream, const T*& v, const void* otherObj) + inline typename TEnableIf::Value>::Type Serialize(ISerializable::SerializeStream& stream, const T* v, const void* otherObj) { stream.Guid(v ? v->GetID() : Guid::Empty); } @@ -470,9 +475,9 @@ namespace Serialization } template - inline typename TEnableIf::Value, bool>::Type ShouldSerialize(const T*& v, const void* otherObj) + inline typename TEnableIf::Value, bool>::Type ShouldSerialize(const T* v, const void* otherObj) { - return !otherObj || ShouldSerialize((const SceneObject*)v, *(const SceneObject**)otherObj); + return !otherObj || ShouldSerializeRef((const SceneObject*)v, *(const SceneObject**)otherObj); } // Scripting Object Reference @@ -480,7 +485,7 @@ namespace Serialization template inline bool ShouldSerialize(const ScriptingObjectReference& v, const void* otherObj) { - return !otherObj || ShouldSerialize(v.Get(), ((ScriptingObjectReference*)otherObj)->Get()); + return !otherObj || ShouldSerializeRef(v.Get(), ((ScriptingObjectReference*)otherObj)->Get()); } template inline void Serialize(ISerializable::SerializeStream& stream, const ScriptingObjectReference& v, const void* otherObj) @@ -501,7 +506,7 @@ namespace Serialization template inline bool ShouldSerialize(const SoftObjectReference& v, const void* otherObj) { - return !otherObj || ShouldSerialize(v.Get(), ((SoftObjectReference*)otherObj)->Get()); + return !otherObj || ShouldSerializeRef(v.Get(), ((SoftObjectReference*)otherObj)->Get()); } template inline void Serialize(ISerializable::SerializeStream& stream, const SoftObjectReference& v, const void* otherObj) diff --git a/Source/Engine/Serialization/SerializationFwd.h b/Source/Engine/Serialization/SerializationFwd.h index 84b51cb39..58ece7fda 100644 --- a/Source/Engine/Serialization/SerializationFwd.h +++ b/Source/Engine/Serialization/SerializationFwd.h @@ -80,7 +80,7 @@ class ISerializeModifier; // Explicit auto-cast for object pointer #define SERIALIZE_OBJ(name) \ - if (Serialization::ShouldSerialize((const ScriptingObject*&)name, other ? &other->name : nullptr)) \ + if (Serialization::ShouldSerialize(name, decltype(name)(other ? &other->name : nullptr))) \ { \ stream.JKEY(#name); \ Serialization::Serialize(stream, (const ScriptingObject*&)name, other ? &other->name : nullptr); \ diff --git a/Source/Engine/Terrain/Terrain.cpp b/Source/Engine/Terrain/Terrain.cpp index 72ed1e7e1..466e81d77 100644 --- a/Source/Engine/Terrain/Terrain.cpp +++ b/Source/Engine/Terrain/Terrain.cpp @@ -852,7 +852,7 @@ void Terrain::OnEnable() GetScene()->Navigation.Actors.Add(this); GetSceneRendering()->AddActor(this, _sceneRenderingKey); #if TERRAIN_USE_PHYSICS_DEBUG - GetSceneRendering()->AddPhysicsDebug(this); + GetSceneRendering()->AddPhysicsDebug(this); #endif void* scene = GetPhysicsScene()->GetPhysicsScene(); for (int32 i = 0; i < _patches.Count(); i++) @@ -873,7 +873,7 @@ void Terrain::OnDisable() GetScene()->Navigation.Actors.Remove(this); GetSceneRendering()->RemoveActor(this, _sceneRenderingKey); #if TERRAIN_USE_PHYSICS_DEBUG - GetSceneRendering()->RemovePhysicsDebug(this); + GetSceneRendering()->RemovePhysicsDebug(this); #endif void* scene = GetPhysicsScene()->GetPhysicsScene(); for (int32 i = 0; i < _patches.Count(); i++) diff --git a/Source/Engine/Terrain/Terrain.h b/Source/Engine/Terrain/Terrain.h index 0b72cd668..5c671629d 100644 --- a/Source/Engine/Terrain/Terrain.h +++ b/Source/Engine/Terrain/Terrain.h @@ -5,6 +5,7 @@ #include "Engine/Content/JsonAssetReference.h" #include "Engine/Content/Assets/MaterialBase.h" #include "Engine/Physics/Actors/PhysicsColliderActor.h" +#include "Engine/Physics/Actors/IPhysicsDebug.h" class Terrain; class TerrainChunk; @@ -38,6 +39,9 @@ struct RenderView; /// /// API_CLASS(Sealed) class FLAXENGINE_API Terrain : public PhysicsColliderActor +#if USE_EDITOR + , public IPhysicsDebug +#endif { DECLARE_SCENE_OBJECT(Terrain); friend Terrain; @@ -441,9 +445,7 @@ public: API_FUNCTION() void DrawChunk(API_PARAM(Ref) const RenderContext& renderContext, API_PARAM(Ref) const Int2& patchCoord, API_PARAM(Ref) const Int2& chunkCoord, MaterialBase* material, int32 lodIndex = 0) const; private: -#if TERRAIN_USE_PHYSICS_DEBUG - void DrawPhysicsDebug(RenderView& view); -#endif + ImplementPhysicsDebug; bool DrawSetup(RenderContext& renderContext); void DrawImpl(RenderContext& renderContext, HashSet& drawnChunks); diff --git a/Source/Engine/Tests/TestPrefabs.cpp b/Source/Engine/Tests/TestPrefabs.cpp index 5e19a5fa0..6389f1cf7 100644 --- a/Source/Engine/Tests/TestPrefabs.cpp +++ b/Source/Engine/Tests/TestPrefabs.cpp @@ -687,6 +687,171 @@ TEST_CASE("Prefabs") instanceA->DeleteObject(); instanceB->DeleteObject(); instanceC->DeleteObject(); + } + SECTION("Test Loading Nested Prefab With Multiple Instances of Nested Prefab") + { + // https://github.com/FlaxEngine/FlaxEngine/issues/3255 + // Create Prefab C with basic cross-object references + AssetReference prefabC = Content::CreateVirtualAsset(); + REQUIRE(prefabC); + Guid id; + Guid::Parse("cccbe4b0416be0777a6ce59e8788b10f", id); + prefabC->ChangeID(id); + auto prefabCInit = prefabC->Init(Prefab::TypeName, + "[" + "{" + "\"ID\": \"aac6b9644492fbca1a6ab0a7904a557e\"," + "\"TypeName\": \"FlaxEngine.ExponentialHeightFog\"," + "\"Name\": \"Prefab C.Root\"," + "\"DirectionalInscatteringLight\": \"bbb6b9644492fbca1a6ab0a7904a557e\"" + "}," + "{" + "\"ID\": \"bbb6b9644492fbca1a6ab0a7904a557e\"," + "\"TypeName\": \"FlaxEngine.DirectionalLight\"," + "\"ParentID\": \"aac6b9644492fbca1a6ab0a7904a557e\"," + "\"Name\": \"Prefab C.Light\"" + "}" + "]"); + REQUIRE(!prefabCInit); + + // Create Prefab B with two nested Prefab C attached to the root + AssetReference prefabB = Content::CreateVirtualAsset(); + REQUIRE(prefabB); + SCOPE_EXIT{ Content::DeleteAsset(prefabB); }; + Guid::Parse("bbb744714f746e31855f41815612d14b", id); + prefabB->ChangeID(id); + auto prefabBInit = prefabB->Init(Prefab::TypeName, + "[" + "{" + "\"ID\": \"244274a04cc60d56a2f024bfeef5772d\"," + "\"TypeName\": \"FlaxEngine.SpotLight\"," + "\"Name\": \"Prefab B.Root\"" + "}," + "{" + "\"ID\": \"1111f1094f430733333f8280e78dfcc3\"," + "\"PrefabID\": \"cccbe4b0416be0777a6ce59e8788b10f\"," + "\"PrefabObjectID\": \"aac6b9644492fbca1a6ab0a7904a557e\"," + "\"ParentID\": \"244274a04cc60d56a2f024bfeef5772d\"" + "}," + "{" + "\"ID\": \"2221f1094f430733333f8280e78dfcc3\"," + "\"PrefabID\": \"cccbe4b0416be0777a6ce59e8788b10f\"," + "\"PrefabObjectID\": \"bbb6b9644492fbca1a6ab0a7904a557e\"," + "\"ParentID\": \"1111f1094f430733333f8280e78dfcc3\"" + "}," + "{" + "\"ID\": \"3331f1094f430733333f8280e78dfcc3\"," + "\"PrefabID\": \"cccbe4b0416be0777a6ce59e8788b10f\"," + "\"PrefabObjectID\": \"aac6b9644492fbca1a6ab0a7904a557e\"," + "\"ParentID\": \"244274a04cc60d56a2f024bfeef5772d\"" + "}," + "{" + "\"ID\": \"4441f1094f430733333f8280e78dfcc3\"," + "\"PrefabID\": \"cccbe4b0416be0777a6ce59e8788b10f\"," + "\"PrefabObjectID\": \"bbb6b9644492fbca1a6ab0a7904a557e\"," + "\"ParentID\": \"3331f1094f430733333f8280e78dfcc3\"" + "}" + "]"); + REQUIRE(!prefabBInit); + + // Create Prefab A as variant of Prefab B (no local changes, just object remapped) + AssetReference prefabA = Content::CreateVirtualAsset(); + REQUIRE(prefabA); + SCOPE_EXIT{ Content::DeleteAsset(prefabA); }; + Guid::Parse("aaa744714f746e31855f41815612d14b", id); + prefabA->ChangeID(id); + auto prefabAInit = prefabA->Init(Prefab::TypeName, + "[" + "{" + "\"ID\": \"123274a04cc60d56a2f024bfeef5772d\"," + "\"PrefabID\": \"bbb744714f746e31855f41815612d14b\"," + "\"PrefabObjectID\": \"244274a04cc60d56a2f024bfeef5772d\"," + "\"Name\": \"Prefab A.Root\"" + "}," + "{" + "\"ID\": \"1211f1094f430733333f8280e78dfcc3\"," + "\"PrefabID\": \"bbb744714f746e31855f41815612d14b\"," + "\"PrefabObjectID\": \"1111f1094f430733333f8280e78dfcc3\"," + "\"ParentID\": \"123274a04cc60d56a2f024bfeef5772d\"" + "}," + "{" + "\"ID\": \"4221f1094f430733333f8280e78dfcc3\"," + "\"PrefabID\": \"bbb744714f746e31855f41815612d14b\"," + "\"PrefabObjectID\": \"2221f1094f430733333f8280e78dfcc3\"," + "\"ParentID\": \"1211f1094f430733333f8280e78dfcc3\"" + "}," + "{" + "\"ID\": \"3131f1094f430733333f8280e78dfcc3\"," + "\"PrefabID\": \"bbb744714f746e31855f41815612d14b\"," + "\"PrefabObjectID\": \"3331f1094f430733333f8280e78dfcc3\"," + "\"ParentID\": \"123274a04cc60d56a2f024bfeef5772d\"" + "}," + "{" + "\"ID\": \"5441f1094f430733333f8280e78dfcc3\"," + "\"PrefabID\": \"bbb744714f746e31855f41815612d14b\"," + "\"PrefabObjectID\": \"4441f1094f430733333f8280e78dfcc3\"," + "\"ParentID\": \"3131f1094f430733333f8280e78dfcc3\"" + "}" + "]"); + REQUIRE(!prefabAInit); + + // Spawn test instances of both prefabs + ScriptingObjectReference instanceA = PrefabManager::SpawnPrefab(prefabA); + ScriptingObjectReference instanceB = PrefabManager::SpawnPrefab(prefabB); + ScriptingObjectReference instanceC = PrefabManager::SpawnPrefab(prefabC); + + // Check state of objects + REQUIRE(instanceC); + REQUIRE(instanceC->Is()); + REQUIRE(instanceC->Children.Count() == 1); + CHECK(instanceC.As()->DirectionalInscatteringLight == instanceC->Children[0]); + REQUIRE(instanceB); + REQUIRE(instanceB->Children.Count() == 2); + ScriptingObjectReference instanceB1 = instanceB->Children[0]; + ScriptingObjectReference instanceB2 = instanceB->Children[1]; + REQUIRE(instanceB1->Is()); + REQUIRE(instanceB1->Children.Count() == 1); + CHECK(instanceB1.As()->DirectionalInscatteringLight == instanceB1->Children[0]); + REQUIRE(instanceB2->Is()); + REQUIRE(instanceB2->Children.Count() == 1); + CHECK(instanceB2.As()->DirectionalInscatteringLight == instanceB2->Children[0]); + REQUIRE(instanceA); + REQUIRE(instanceA->Children.Count() == 2); + ScriptingObjectReference instanceA1 = instanceA->Children[0]; + ScriptingObjectReference instanceA2 = instanceA->Children[1]; + REQUIRE(instanceA1->Is()); + REQUIRE(instanceA1->Children.Count() == 1); + CHECK(instanceA1.As()->DirectionalInscatteringLight == instanceA1->Children[0]); + REQUIRE(instanceA2->Is()); + REQUIRE(instanceA1->Children.Count() == 1); + CHECK(instanceA2.As()->DirectionalInscatteringLight == instanceA2->Children[0]); + + // Add instance of Prefab C to Prefab B + instanceC->SetName(StringView(TEXT("New"))); + instanceC->SetParent(instanceB); + bool applyResult = PrefabManager::ApplyAll(instanceB); + REQUIRE(!applyResult); + + // Check if Prefab A reflects that change + REQUIRE(instanceA); + REQUIRE(instanceA->Children.Count() == 3); + instanceA1 = instanceA->Children[0]; + instanceA2 = instanceA->Children[1]; + ScriptingObjectReference instanceA3 = instanceA->Children[2]; + REQUIRE(instanceA1->Is()); + REQUIRE(instanceA1->Children.Count() == 1); + CHECK(instanceA1.As()->DirectionalInscatteringLight == instanceA1->Children[0]); + REQUIRE(instanceA2->Is()); + REQUIRE(instanceA2->Children.Count() == 1); + CHECK(instanceA2.As()->DirectionalInscatteringLight == instanceA2->Children[0]); + REQUIRE(instanceA3->Is()); + REQUIRE(instanceA3->Children.Count() == 1); + CHECK(instanceA3.As()->DirectionalInscatteringLight == instanceA3->Children[0]); + + // Cleanup + instanceA->DeleteObject(); + instanceB->DeleteObject(); + instanceC->DeleteObject(); } } diff --git a/Source/Engine/UI/TextRender.cpp b/Source/Engine/UI/TextRender.cpp index 6b2988b7a..3a0d03c68 100644 --- a/Source/Engine/UI/TextRender.cpp +++ b/Source/Engine/UI/TextRender.cpp @@ -172,7 +172,7 @@ void TextRender::UpdateLayout() // Pick a font (remove DPI text scale as the text is being placed in the world) auto font = Font->CreateFont(_size); - float scale = 1.0f / FontManager::FontScale; + float scale = _layoutOptions.Scale / FontManager::FontScale; // Prepare FontTextureAtlas* fontAtlas = nullptr; diff --git a/Source/Shaders/Lighting.hlsl b/Source/Shaders/Lighting.hlsl index 9b14db5ed..4d6e474fa 100644 --- a/Source/Shaders/Lighting.hlsl +++ b/Source/Shaders/Lighting.hlsl @@ -122,10 +122,6 @@ float4 GetLighting(float3 viewPos, LightData lightData, GBufferSample gBuffer, f // Calculate shadow ShadowSample shadow = GetShadow(lightData, gBuffer, shadowMask); -#if !LIGHTING_NO_DIRECTIONAL - // Directional shadowing - shadow.SurfaceShadow *= NoL; -#endif // Calculate attenuation if (isRadial) @@ -139,6 +135,11 @@ float4 GetLighting(float3 viewPos, LightData lightData, GBufferSample gBuffer, f shadow.TransmissionShadow *= attenuation; } +#if !LIGHTING_NO_DIRECTIONAL + // Directional shadowing + shadow.SurfaceShadow *= NoL; +#endif + BRANCH if (shadow.SurfaceShadow + shadow.TransmissionShadow > 0) { diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs index 7841b98c3..14058aaf5 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs @@ -13,6 +13,7 @@ namespace Flax.Build.Bindings public FileInfo File; public Tokenizer Tokenizer; public ApiTypeInfo ScopeInfo; + public NativeCpp.BuildOptions ModuleOptions; public AccessLevel CurrentAccessLevel; public Stack ScopeTypeStack; public Stack ScopeAccessStack; @@ -534,6 +535,12 @@ namespace Flax.Build.Bindings } if (token.Type == TokenType.LeftCurlyBrace) break; + if (token.Type == TokenType.Preprocessor) + { + OnPreProcessorToken(ref context, ref token); + while (token.Type == TokenType.Newline || token.Value == "endif") + token = context.Tokenizer.NextToken(); + } if (token.Type == TokenType.Colon) { token = context.Tokenizer.ExpectToken(TokenType.Colon); diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.cs index 9ecb5ecd3..16d41b32e 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.cs @@ -245,6 +245,7 @@ namespace Flax.Build.Bindings File = fileInfo, Tokenizer = tokenizer, ScopeInfo = null, + ModuleOptions = moduleOptions, CurrentAccessLevel = AccessLevel.Public, ScopeTypeStack = new Stack(), ScopeAccessStack = new Stack(), @@ -380,110 +381,7 @@ namespace Flax.Build.Bindings // Handle preprocessor blocks if (token.Type == TokenType.Preprocessor) { - token = tokenizer.NextToken(); - switch (token.Value) - { - case "define": - { - token = tokenizer.NextToken(); - var name = token.Value; - var value = string.Empty; - token = tokenizer.NextToken(true); - while (token.Type != TokenType.Newline) - { - value += token.Value; - token = tokenizer.NextToken(true); - } - value = value.Trim(); - context.PreprocessorDefines[name] = value; - break; - } - case "if": - case "elif": - { - // Parse condition - var condition = string.Empty; - token = tokenizer.NextToken(true); - while (token.Type != TokenType.Newline) - { - var tokenValue = token.Value.Trim(); - if (tokenValue.Length == 0) - { - token = tokenizer.NextToken(true); - continue; - } - - // Very simple defines processing - var negate = tokenValue[0] == '!'; - if (negate) - tokenValue = tokenValue.Substring(1); - tokenValue = ReplacePreProcessorDefines(tokenValue, context.PreprocessorDefines); - tokenValue = ReplacePreProcessorDefines(tokenValue, moduleOptions.PublicDefinitions); - tokenValue = ReplacePreProcessorDefines(tokenValue, moduleOptions.PrivateDefinitions); - tokenValue = ReplacePreProcessorDefines(tokenValue, moduleOptions.CompileEnv.PreprocessorDefinitions); - tokenValue = tokenValue.Replace("false", "0"); - tokenValue = tokenValue.Replace("true", "1"); - tokenValue = tokenValue.Replace("||", "|"); - if (tokenValue.Length != 0 && tokenValue != "1" && tokenValue != "0" && tokenValue != "|") - tokenValue = "0"; - if (negate) - tokenValue = tokenValue == "1" ? "0" : "1"; - - condition += tokenValue; - token = tokenizer.NextToken(true); - } - - // Filter condition - bool modified; - do - { - modified = false; - if (condition.Contains("1|1")) - { - condition = condition.Replace("1|1", "1"); - modified = true; - } - if (condition.Contains("1|0")) - { - condition = condition.Replace("1|0", "1"); - modified = true; - } - if (condition.Contains("0|1")) - { - condition = condition.Replace("0|1", "1"); - modified = true; - } - } while (modified); - - // Skip chunk of code of condition fails - if (condition != "1") - { - ParsePreprocessorIf(fileInfo, tokenizer, ref token); - } - - break; - } - case "ifdef": - { - // Parse condition - var define = string.Empty; - token = tokenizer.NextToken(true); - while (token.Type != TokenType.Newline) - { - define += token.Value; - token = tokenizer.NextToken(true); - } - - // Check condition - define = define.Trim(); - if (!context.PreprocessorDefines.ContainsKey(define) && !moduleOptions.CompileEnv.PreprocessorDefinitions.Contains(define)) - { - ParsePreprocessorIf(fileInfo, tokenizer, ref token); - } - - break; - } - } + OnPreProcessorToken(ref context, ref token); } // Scope tracking @@ -514,6 +412,120 @@ namespace Flax.Build.Bindings } } + private static void OnPreProcessorToken(ref ParsingContext context, ref Token token) + { + var tokenizer = context.Tokenizer; + token = tokenizer.NextToken(); + switch (token.Value) + { + case "define": + { + token = tokenizer.NextToken(); + var name = token.Value; + var value = string.Empty; + token = tokenizer.NextToken(true); + while (token.Type != TokenType.Newline) + { + value += token.Value; + token = tokenizer.NextToken(true); + } + value = value.Trim(); + context.PreprocessorDefines[name] = value; + break; + } + case "if": + case "elif": + { + // Parse condition + var condition = string.Empty; + token = tokenizer.NextToken(true); + while (token.Type != TokenType.Newline) + { + var tokenValue = token.Value.Trim(); + if (tokenValue.Length == 0) + { + token = tokenizer.NextToken(true); + continue; + } + + // Very simple defines processing + var negate = tokenValue[0] == '!'; + if (negate) + tokenValue = tokenValue.Substring(1); + tokenValue = ReplacePreProcessorDefines(tokenValue, context.PreprocessorDefines); + tokenValue = ReplacePreProcessorDefines(tokenValue, context.ModuleOptions.PublicDefinitions); + tokenValue = ReplacePreProcessorDefines(tokenValue, context.ModuleOptions.PrivateDefinitions); + tokenValue = ReplacePreProcessorDefines(tokenValue, context.ModuleOptions.CompileEnv.PreprocessorDefinitions); + tokenValue = tokenValue.Replace("false", "0"); + tokenValue = tokenValue.Replace("true", "1"); + tokenValue = tokenValue.Replace("||", "|"); + if (tokenValue.Length != 0 && tokenValue != "1" && tokenValue != "0" && tokenValue != "|") + tokenValue = "0"; + if (negate) + tokenValue = tokenValue == "1" ? "0" : "1"; + + condition += tokenValue; + token = tokenizer.NextToken(true); + } + + // Filter condition + bool modified; + do + { + modified = false; + if (condition.Contains("1|1")) + { + condition = condition.Replace("1|1", "1"); + modified = true; + } + if (condition.Contains("1|0")) + { + condition = condition.Replace("1|0", "1"); + modified = true; + } + if (condition.Contains("0|1")) + { + condition = condition.Replace("0|1", "1"); + modified = true; + } + } while (modified); + + // Skip chunk of code of condition fails + if (condition != "1") + { + ParsePreprocessorIf(context.File, tokenizer, ref token); + } + + break; + } + case "ifdef": + { + // Parse condition + var define = string.Empty; + token = tokenizer.NextToken(true); + while (token.Type != TokenType.Newline) + { + define += token.Value; + token = tokenizer.NextToken(true); + } + + // Check condition + define = define.Trim(); + if (!context.PreprocessorDefines.ContainsKey(define) && !context.ModuleOptions.CompileEnv.PreprocessorDefinitions.Contains(define)) + { + ParsePreprocessorIf(context.File, tokenizer, ref token); + } + + break; + } + case "endif": + { + token = tokenizer.NextToken(true); + break; + } + } + } + private static string ReplacePreProcessorDefines(string text, IEnumerable defines) { foreach (var define in defines)