313 lines
11 KiB
C#
313 lines
11 KiB
C#
// Copyright (c) Wojciech Figat. All rights reserved.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using FlaxEditor.SceneGraph;
|
|
using FlaxEngine;
|
|
using FlaxEngine.Utilities;
|
|
|
|
namespace FlaxEditor.Actions
|
|
{
|
|
/// <summary>
|
|
/// Implementation of <see cref="IUndoAction"/> used to delete a selection of <see cref="ActorNode"/>.
|
|
/// </summary>
|
|
/// <seealso cref="FlaxEditor.IUndoAction" />
|
|
[Serializable]
|
|
class DeleteActorsAction : IUndoAction
|
|
{
|
|
[Serialize]
|
|
private byte[] _actorsData;
|
|
|
|
[Serialize]
|
|
private List<SceneGraphNode.StateData> _nodesData;
|
|
|
|
[Serialize]
|
|
private Guid[] _nodeParentsIDs;
|
|
|
|
[Serialize]
|
|
private int[] _nodeParentsOrders;
|
|
|
|
[Serialize]
|
|
private Guid[] _prefabIds;
|
|
|
|
[Serialize]
|
|
private Guid[] _prefabObjectIds;
|
|
|
|
[Serialize]
|
|
private bool _isInverted;
|
|
|
|
[Serialize]
|
|
private bool _affectsCSG;
|
|
|
|
[Serialize]
|
|
private bool _affectsNavigation;
|
|
|
|
[Serialize]
|
|
protected List<SceneGraphNode> _nodeParents;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="DeleteActorsAction"/> class.
|
|
/// </summary>
|
|
/// <param name="actor">The actor.</param>
|
|
/// <param name="isInverted">If set to <c>true</c> action will be inverted - instead of delete it will create actors.</param>
|
|
/// <param name="preserveOrder">If set to <c>true</c> action will be preserve actors order when performing undo.</param>
|
|
internal DeleteActorsAction(Actor actor, bool isInverted = false, bool preserveOrder = true)
|
|
: this(new List<SceneGraphNode>(1) { SceneGraphFactory.FindNode(actor.ID) }, isInverted, preserveOrder)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="DeleteActorsAction"/> class.
|
|
/// </summary>
|
|
/// <param name="node">The object.</param>
|
|
/// <param name="isInverted">If set to <c>true</c> action will be inverted - instead of delete it will create actors.</param>
|
|
/// <param name="preserveOrder">If set to <c>true</c> action will be preserve actors order when performing undo.</param>
|
|
internal DeleteActorsAction(SceneGraphNode node, bool isInverted = false, bool preserveOrder = true)
|
|
: this(new List<SceneGraphNode>(1) { node }, isInverted, preserveOrder)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="DeleteActorsAction"/> class.
|
|
/// </summary>
|
|
/// <param name="nodes">The objects.</param>
|
|
/// <param name="isInverted">If set to <c>true</c> action will be inverted - instead of delete it will create actors.</param>
|
|
/// <param name="preserveOrder">If set to <c>true</c> action will be preserve actors order when performing undo.</param>
|
|
internal DeleteActorsAction(List<SceneGraphNode> nodes, bool isInverted = false, bool preserveOrder = true)
|
|
{
|
|
_isInverted = isInverted;
|
|
|
|
// Collect nodes to delete
|
|
var deleteNodes = new List<SceneGraphNode>(nodes.Count);
|
|
var actors = new List<Actor>(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<SceneGraphNode.StateData>();
|
|
_nodesData.Add(node.State);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Collect parent nodes to delete
|
|
_nodeParents = new List<SceneGraphNode>(nodes.Count);
|
|
deleteNodes.BuildNodesParents(_nodeParents);
|
|
OnDirtyInit();
|
|
_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());
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public string ActionString => _isInverted ? "Create actors" : "Delete actors";
|
|
|
|
/// <inheritdoc />
|
|
public void Do()
|
|
{
|
|
if (_isInverted)
|
|
Create();
|
|
else
|
|
Delete();
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public void Undo()
|
|
{
|
|
if (_isInverted)
|
|
Delete();
|
|
else
|
|
Create();
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public void Dispose()
|
|
{
|
|
_actorsData = null;
|
|
_nodeParentsIDs = null;
|
|
_nodeParentsOrders = null;
|
|
_prefabIds = null;
|
|
_prefabObjectIds = null;
|
|
_nodeParents.Clear();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Deletes the objects.
|
|
/// </summary>
|
|
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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the node.
|
|
/// </summary>
|
|
/// <param name="id">The actor id.</param>
|
|
/// <returns>The scene graph node.</returns>
|
|
protected virtual SceneGraphNode GetNode(Guid id)
|
|
{
|
|
return SceneGraphFactory.FindNode(id);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates the removed objects (from data).
|
|
/// </summary>
|
|
protected virtual void Create()
|
|
{
|
|
var nodes = new List<SceneGraphNode>();
|
|
|
|
// 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]), prefabId, _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);
|
|
if (_nodeParentsOrders != null)
|
|
node.Actor.OrderInParent = _nodeParentsOrders[i];
|
|
}
|
|
}
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|