// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; using System.Collections.Generic; using FlaxEditor.SceneGraph; using FlaxEditor.Scripting; using FlaxEngine; using FlaxEngine.Utilities; namespace FlaxEditor.Actions { /// /// Implementation of used to delete a selection of . /// /// [Serializable] class DeleteActorsAction : IUndoAction { [Serialize] private byte[] _actorsData; [Serialize] private List _nodesData; [Serialize] private Guid[] _nodeParentsIDs; [Serialize] private Guid[] _prefabIds; [Serialize] private Guid[] _prefabObjectIds; [Serialize] private bool _isInverted; [Serialize] private bool _affectsCSG; [Serialize] private bool _affectsNavigation; [Serialize] protected List _nodeParents; /// /// 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) { _isInverted = isInverted; // Collect nodes to delete var deleteNodes = new List(nodes.Count); var actors = new List(nodes.Count); for (int i = 0; i < nodes.Count; i++) { var node = nodes[i]; if (node is ActorNode actorNode) { deleteNodes.Add(actorNode); actors.Add(actorNode.Actor); } else { deleteNodes.Add(node); if (node.CanUseState) { if (_nodesData == null) _nodesData = new List(); _nodesData.Add(node.State); } } } // Collect parent nodes to delete _nodeParents = new List(nodes.Count); deleteNodes.BuildNodesParents(_nodeParents); OnDirtyInit(); _nodeParentsIDs = new Guid[_nodeParents.Count]; for (int i = 0; i < _nodeParentsIDs.Length; i++) _nodeParentsIDs[i] = _nodeParents[i].ID; // Serialize actors _actorsData = Actor.ToBytes(actors.ToArray()); // Cache actors linkage to prefab objects _prefabIds = new Guid[actors.Count]; _prefabObjectIds = new Guid[actors.Count]; for (int i = 0; i < actors.Count; i++) { _prefabIds[i] = actors[i].PrefabID; _prefabObjectIds[i] = actors[i].PrefabObjectID; } } /// public string ActionString => _isInverted ? "Create actors" : "Delete actors"; /// public void Do() { if (_isInverted) Create(); else Delete(); } /// public void Undo() { if (_isInverted) Delete(); else Create(); } /// public void Dispose() { _actorsData = null; _nodeParentsIDs = null; _prefabIds = null; _prefabObjectIds = null; _nodeParents.Clear(); } /// /// Deletes the objects. /// protected virtual void Delete() { // Remove objects OnDirty(); for (int i = 0; i < _nodeParents.Count; i++) { var node = _nodeParents[i]; node.Delete(); } _nodeParents.Clear(); FlaxEngine.Scripting.FlushRemovedObjects(); } /// /// Gets the node. /// /// The actor id. /// The scene graph node. protected virtual SceneGraphNode GetNode(Guid id) { return SceneGraphFactory.FindNode(id); } /// /// Creates the removed objects (from data). /// protected virtual void Create() { var nodes = new List(); // Restore actors var actors = Actor.FromBytes(_actorsData); if (actors != null) { nodes.Capacity = Math.Max(nodes.Capacity, actors.Length); // Preserve prefab objects linkage for (int i = 0; i < actors.Length; i++) { Guid prefabId = _prefabIds[i]; if (prefabId != Guid.Empty) { Actor.Internal_LinkPrefab(FlaxEngine.Object.GetUnmanagedPtr(actors[i]), ref prefabId, ref _prefabObjectIds[i]); } } } // Restore nodes state if (_nodesData != null) { for (int i = 0; i < _nodesData.Count; i++) { var state = _nodesData[i]; var type = TypeUtils.GetManagedType(state.TypeName); if (type == null) { Editor.LogError($"Missing type {state.TypeName} for scene graph node undo state restore."); continue; } var method = type.GetMethod(state.CreateMethodName); if (method == null) { Editor.LogError($"Missing method {state.CreateMethodName} from type {state.TypeName} for scene graph node undo state restore."); continue; } var node = method.Invoke(null, new object[] { state }); if (node == null) { Editor.LogError($"Failed to restore scene graph node state via method {state.CreateMethodName} from type {state.TypeName}."); continue; } } } // Cache parent nodes ids for (int i = 0; i < _nodeParentsIDs.Length; i++) { var foundNode = GetNode(_nodeParentsIDs[i]); if (foundNode is ActorNode node) { nodes.Add(node); } } nodes.BuildNodesParents(_nodeParents); OnDirty(); } private void OnDirtyInit() { for (int i = 0; i < _nodeParents.Count; i++) { if (_nodeParents[i] is ActorNode node && node.Actor is BoxBrush) { _affectsCSG = true; break; } } for (int i = 0; i < _nodeParents.Count; i++) { if (_nodeParents[i] is ActorNode actorNode && actorNode.AffectsNavigationWithChildren) { _affectsNavigation = true; break; } } } private void OnDirty() { // Mark scene as modified foreach (var obj in _nodeParents) { Editor.Instance.Scene.MarkSceneEdited(obj.ParentScene); } var editor = Editor.Instance; if (editor.StateMachine.IsPlayMode) return; var options = editor.Options.Options; // Auto CSG mesh rebuild if (_affectsCSG && options.General.AutoRebuildCSG) { for (var i = 0; i < _nodeParents.Count; i++) { if (_nodeParents[i] is ActorNode node && node.Actor is BoxBrush) node.Actor.Scene.BuildCSG(options.General.AutoRebuildCSGTimeoutMs); } } // Auto NavMesh rebuild if (_affectsNavigation && options.General.AutoRebuildNavMesh) { for (var i = 0; i < _nodeParents.Count; i++) { if (_nodeParents[i] is ActorNode node && node.Actor && node.Actor.Scene && node.AffectsNavigationWithChildren) { var bounds = node.Actor.BoxWithChildren; Navigation.BuildNavMesh(node.Actor.Scene, bounds, options.General.AutoRebuildNavMeshTimeoutMs); } } } } } }