// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; using System.Collections.Generic; using FlaxEditor.Modules; using FlaxEditor.SceneGraph; using FlaxEngine; namespace FlaxEditor { /// /// Implementation of used to transform a selection of . /// The same logic could be achieved using but it would be slower. /// Since we use this kind of action very ofter (for operations) it's better to provide faster implementation. /// /// [Serializable] public sealed class TransformObjectsAction : UndoActionBase, ISceneEditAction { /// /// The undo data. /// [Serializable] public struct DataStorage { /// /// The scene of the selected objects. /// public Scene Scene; /// /// The selection pool. /// public SceneGraphNode[] Selection; /// /// The 'before' state. /// public Transform[] Before; /// /// The 'after' state. /// public Transform[] After; /// /// The cached bounding box that contains all selected items in 'before' state. /// public BoundingBox BeforeBounds; /// /// The cached bounding box that contains all selected items in 'after' state. /// public BoundingBox AfterBounds; /// /// True if navigation system has been modified during editing the selected objects (navmesh auto-rebuild is required). /// public bool NavigationDirty; } internal TransformObjectsAction(List selection, List before, ref BoundingBox boundsBefore, bool navigationDirty) { var after = Utilities.Utils.GetTransformsAndBounds(selection, out var afterBounds); // TODO: support moving objects from more than one scene var scene = selection[0].ParentScene?.Scene; var data = new DataStorage { Scene = scene, Selection = selection.ToArray(), After = after, Before = before.ToArray(), BeforeBounds = boundsBefore, AfterBounds = afterBounds, NavigationDirty = navigationDirty, }; Data = data; InvalidateBounds(ref data); } /// public override string ActionString => "Transform object(s)"; /// public override void Do() { var data = Data; for (int i = 0; i < data.Selection.Length; i++) { data.Selection[i].Transform = data.After[i]; } InvalidateBounds(ref data); } /// public override void Undo() { var data = Data; for (int i = 0; i < data.Selection.Length; i++) { data.Selection[i].Transform = data.Before[i]; } InvalidateBounds(ref data); } private void InvalidateBounds(ref DataStorage data) { if (!data.NavigationDirty) return; var editor = Editor.Instance; bool isPlayMode = editor.StateMachine.IsPlayMode; var options = editor.Options.Options; // Auto NavMesh rebuild if (!isPlayMode && options.General.AutoRebuildNavMesh && data.Scene != null) { // Handle simple case where objects were moved just a little and use one navmesh build request to improve performance if (data.BeforeBounds.Intersects(ref data.AfterBounds)) { Navigation.BuildNavMesh(data.Scene, BoundingBox.Merge(data.BeforeBounds, data.AfterBounds), options.General.AutoRebuildNavMeshTimeoutMs); } else { Navigation.BuildNavMesh(data.Scene, data.BeforeBounds, options.General.AutoRebuildNavMeshTimeoutMs); Navigation.BuildNavMesh(data.Scene, data.AfterBounds, options.General.AutoRebuildNavMeshTimeoutMs); } } } void ISceneEditAction.MarkSceneEdited(SceneModule sceneModule) { var data = Data; for (int i = 0; i < data.Selection.Length; i++) { sceneModule.MarkSceneEdited(data.Selection[i].ParentScene); } } } }