GameObjectUndo.cs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599
  1. //********************************** Banshee Engine (www.banshee3d.com) **************************************************//
  2. //**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Runtime.CompilerServices;
  6. using bs;
  7. namespace bs.Editor
  8. {
  9. /** @addtogroup Utility-Editor
  10. * @{
  11. */
  12. /// <summary>
  13. /// Handles undo & redo operations for changes made on game objects. Game objects can be recorded just before a change
  14. /// is made and the system will calculate the difference between that state and the state at the end of current frame.
  15. /// This difference will then be recorded as a undo/redo operation. The undo/redo operation will also take care of
  16. /// selecting the object & field it is acting upon.
  17. /// </summary>
  18. internal class GameObjectUndo
  19. {
  20. /// <summary>
  21. /// Contains information about a component that needs its diff recorded.
  22. /// </summary>
  23. private struct ComponentToRecord
  24. {
  25. private Component obj;
  26. private string path;
  27. private SerializedObject orgState;
  28. /// <summary>
  29. /// Creates a new object instance, recording the current state of the component.
  30. /// </summary>
  31. /// <param name="obj">Component to record the state of.</param>
  32. /// <param name="path">
  33. /// Path to the field which should be focused when performing the undo/redo operation. This should be the path
  34. /// as provided by <see cref="InspectableField"/>.
  35. /// </param>
  36. internal ComponentToRecord(Component obj, string path)
  37. {
  38. this.obj = obj;
  39. this.path = path;
  40. orgState = SerializedObject.Create(obj);
  41. }
  42. /// <summary>
  43. /// Generates the diff from the previously recorded state and the current state. If there is a difference
  44. /// an undo command is recorded.
  45. /// </summary>
  46. internal void RecordCommand()
  47. {
  48. if (obj.IsDestroyed)
  49. return;
  50. SerializedObject newState = SerializedObject.Create(obj);
  51. SerializedDiff oldToNew = SerializedDiff.Create(orgState, newState);
  52. if (oldToNew == null || oldToNew.IsEmpty)
  53. return;
  54. SerializedDiff newToOld = SerializedDiff.Create(newState, orgState);
  55. UndoRedo.Global.RegisterCommand(new RecordComponentUndo(obj, path, oldToNew, newToOld));
  56. }
  57. }
  58. /// <summary>
  59. /// Contains information about a scene object that needs its diff recorded. Note this will not record the entire
  60. /// scene object, but rather just its name, transform, active state and potentially other similar properties.
  61. /// It's components as well as hierarchy state are ignored.
  62. /// </summary>
  63. private struct SceneObjectHeaderToRecord
  64. {
  65. private SceneObject obj;
  66. private string path;
  67. private SceneObjectState orgState;
  68. /// <summary>
  69. /// Creates a new object instance, recording the current state of the scene object header.
  70. /// </summary>
  71. /// <param name="obj">Scene object to record the state of.</param>
  72. /// <param name="path">
  73. /// Path to the field which should be focused when performing the undo/redo operation.
  74. /// </param>
  75. internal SceneObjectHeaderToRecord(SceneObject obj, string path)
  76. {
  77. this.obj = obj;
  78. this.path = path;
  79. orgState = SceneObjectState.Create(obj);
  80. }
  81. /// <summary>
  82. /// Generates the diff from the previously recorded state and the current state. If there is a difference
  83. /// an undo command is recorded.
  84. /// </summary>
  85. internal void RecordCommand()
  86. {
  87. if (obj.IsDestroyed)
  88. return;
  89. SceneObjectDiff oldToNew = SceneObjectDiff.Create(orgState, SceneObjectState.Create(obj));
  90. if (oldToNew.flags == 0)
  91. return;
  92. SceneObjectDiff newToOld = SceneObjectDiff.Create(SceneObjectState.Create(obj), orgState);
  93. UndoRedo.Global.RegisterCommand(new RecordSceneObjectHeaderUndo(obj, path, oldToNew, newToOld));
  94. }
  95. }
  96. /// <summary>
  97. /// Contains information about a scene object that needs its diff recorded. Unlike
  98. /// <see cref="SceneObjectHeaderToRecord"/> this will record the entire scene object,
  99. /// including its components and optionally the child hierarchy.
  100. /// </summary>
  101. private struct SceneObjectToRecord
  102. {
  103. private SceneObject obj;
  104. private string description;
  105. private SerializedSceneObject orgState;
  106. /// <summary>
  107. /// Creates a new object instance, recording the current state of the scene object.
  108. /// </summary>
  109. /// <param name="obj">Scene object to record the state of.</param>
  110. /// <param name="hierarchy">If true, the child scene objects will be recorded as well.</param>
  111. /// <param name="description">
  112. /// Optional description that describes the change that is happening.
  113. /// </param>
  114. internal SceneObjectToRecord(SceneObject obj, bool hierarchy, string description)
  115. {
  116. this.obj = obj;
  117. this.description = description;
  118. orgState = new SerializedSceneObject(obj, hierarchy);
  119. }
  120. /// <summary>
  121. /// Generates the diff from the previously recorded state and the current state. If there is a difference
  122. /// an undo command is recorded.
  123. /// </summary>
  124. internal void RecordCommand()
  125. {
  126. if (obj.IsDestroyed)
  127. return;
  128. var newState = new SerializedSceneObject(obj);
  129. UndoRedo.Global.RegisterCommand(new RecordSceneObjectUndo(obj, orgState, newState, description));
  130. }
  131. }
  132. private static List<ComponentToRecord> components = new List<ComponentToRecord>();
  133. private static List<SceneObjectHeaderToRecord> sceneObjectHeaders = new List<SceneObjectHeaderToRecord>();
  134. private static List<SceneObjectToRecord> sceneObjects = new List<SceneObjectToRecord>();
  135. /// <summary>
  136. /// Records the current state of the provided component, and generates a diff with the next state at the end of the
  137. /// frame. If change is detected an undo operation will be recorded. Generally you want to call this just before
  138. /// you are about to make a change to the component.
  139. /// </summary>
  140. /// <param name="obj">Component to record the state of.</param>
  141. /// <param name="fieldPath">
  142. /// Path to the field which should be focused when performing the undo/redo operation. This should be the path
  143. /// as provided by <see cref="InspectableField"/>.
  144. /// </param>
  145. public static void RecordComponent(Component obj, string fieldPath)
  146. {
  147. ComponentToRecord cmp = new ComponentToRecord(obj, fieldPath);
  148. components.Add(cmp);
  149. }
  150. /// <summary>
  151. /// Records the current state of the provided scene object header, and generates a diff with the next state at the
  152. /// end of the frame. If change is detected an undo operation will be recorded. Generally you want to call this
  153. /// just before you are about to make a change to the scene object header.
  154. ///
  155. /// Note this will not record the entire scene object, but rather just its name, transform, active state and
  156. /// potentially other similar properties. It's components as well as hierarchy state are ignored.
  157. /// </summary>
  158. /// <param name="obj">Scene object to record the state of.</param>
  159. /// <param name="fieldName">
  160. /// Name to the field which should be focused when performing the undo/redo operation.
  161. /// </param>
  162. public static void RecordSceneObjectHeader(SceneObject obj, string fieldName)
  163. {
  164. SceneObjectHeaderToRecord so = new SceneObjectHeaderToRecord(obj, fieldName);
  165. sceneObjectHeaders.Add(so);
  166. }
  167. /// <summary>
  168. /// Records the current state of the provided scene object header, and generates a diff with the next state at the
  169. /// end of the frame. If change is detected an undo operation will be recorded. Generally you want to call this
  170. /// just before you are about to make a change to the scene object.
  171. ///
  172. /// Note this records the complete state of a scene object, including its header, its components and optionally its
  173. /// child hierarchy.
  174. /// </summary>
  175. /// <param name="obj">Scene object to record.</param>
  176. /// <param name="hierarchy">
  177. /// If true the child objects will be recorded as well, otherwise just the provided object.
  178. /// </param>
  179. /// <param name="description">Optional description specifying the type of changes about to be made.</param>
  180. public static void RecordSceneObject(SceneObject obj, bool hierarchy, string description)
  181. {
  182. SceneObjectToRecord so = new SceneObjectToRecord(obj, hierarchy, description);
  183. sceneObjects.Add(so);
  184. }
  185. /// <summary>
  186. /// Generates diffs for any objects that were previously recorded using any of the Record* methods. The diff is
  187. /// generated by comparing the state at the time Record* was called, compared to the current object state.
  188. /// </summary>
  189. public static void ResolveDiffs()
  190. {
  191. foreach (var entry in components)
  192. entry.RecordCommand();
  193. foreach (var entry in sceneObjectHeaders)
  194. entry.RecordCommand();
  195. foreach (var entry in sceneObjects)
  196. entry.RecordCommand();
  197. components.Clear();
  198. sceneObjectHeaders.Clear();
  199. sceneObjects.Clear();
  200. }
  201. }
  202. /// <summary>
  203. /// Contains information about scene object state, excluding information about its components and hierarchy.
  204. /// </summary>
  205. internal struct SceneObjectState
  206. {
  207. internal string name;
  208. internal Vector3 position;
  209. internal Quaternion rotation;
  210. internal Vector3 scale;
  211. internal bool active;
  212. /// <summary>
  213. /// Initializes the state from a scene object.
  214. /// </summary>
  215. /// <param name="so">Scene object to initialize the state from.</param>
  216. /// <returns>New state object.</returns>
  217. internal static SceneObjectState Create(SceneObject so)
  218. {
  219. SceneObjectState state = new SceneObjectState();
  220. state.name = so.Name;
  221. state.position = so.LocalPosition;
  222. state.rotation = so.LocalRotation;
  223. state.scale = so.LocalScale;
  224. state.active = so.Active;
  225. return state;
  226. }
  227. }
  228. /// <summary>
  229. /// Contains the difference between two <see cref="SceneObjectState"/> objects and allows the changes to be applied to
  230. /// a <see cref="SceneObject"/>. The value of the different fields is stored as its own state, while the flags field
  231. /// specified which of the properties is actually different.
  232. /// </summary>
  233. internal struct SceneObjectDiff
  234. {
  235. internal SceneObjectState state;
  236. internal SceneObjectDiffFlags flags;
  237. /// <summary>
  238. /// Creates a diff object storing the difference between two <see cref="SceneObjectState"/> objects.
  239. /// </summary>
  240. /// <param name="oldState">State of the scene object to compare from.</param>
  241. /// <param name="newState">State of the scene object to compare to.</param>
  242. /// <returns>Difference between the two scene object states.</returns>
  243. internal static SceneObjectDiff Create(SceneObjectState oldState, SceneObjectState newState)
  244. {
  245. SceneObjectDiff diff = new SceneObjectDiff();
  246. diff.state = new SceneObjectState();
  247. if (oldState.name != newState.name)
  248. {
  249. diff.state.name = newState.name;
  250. diff.flags |= SceneObjectDiffFlags.Name;
  251. }
  252. if (oldState.position != newState.position)
  253. {
  254. diff.state.position = newState.position;
  255. diff.flags |= SceneObjectDiffFlags.Position;
  256. }
  257. if (oldState.rotation != newState.rotation)
  258. {
  259. diff.state.rotation = newState.rotation;
  260. diff.flags |= SceneObjectDiffFlags.Rotation;
  261. }
  262. if (oldState.scale != newState.scale)
  263. {
  264. diff.state.scale = newState.scale;
  265. diff.flags |= SceneObjectDiffFlags.Scale;
  266. }
  267. if (oldState.active != newState.active)
  268. {
  269. diff.state.active = newState.active;
  270. diff.flags |= SceneObjectDiffFlags.Active;
  271. }
  272. return diff;
  273. }
  274. /// <summary>
  275. /// Applies the diff to an actual scene object.
  276. /// </summary>
  277. /// <param name="sceneObject">Scene object to apply the diff to.</param>
  278. internal void Apply(SceneObject sceneObject)
  279. {
  280. if (flags.HasFlag(SceneObjectDiffFlags.Name))
  281. sceneObject.Name = state.name;
  282. if (flags.HasFlag(SceneObjectDiffFlags.Position))
  283. sceneObject.LocalPosition = state.position;
  284. if (flags.HasFlag(SceneObjectDiffFlags.Rotation))
  285. sceneObject.LocalRotation = state.rotation;
  286. if (flags.HasFlag(SceneObjectDiffFlags.Scale))
  287. sceneObject.LocalScale = state.scale;
  288. if (flags.HasFlag(SceneObjectDiffFlags.Active))
  289. sceneObject.Active = state.active;
  290. }
  291. }
  292. /// <summary>
  293. /// Stores the field changes in a <see cref="SceneObject"/> as a difference between two states. Allows those changes to
  294. /// be reverted and re-applied. Does not record changes to scene object components or hierarchy, but just the fields
  295. /// considered its header (such as name, local transform and active state).
  296. /// </summary>
  297. [SerializeObject]
  298. internal class RecordSceneObjectHeaderUndo : UndoableCommand
  299. {
  300. private SceneObject obj;
  301. private string fieldPath;
  302. private SceneObjectDiff newToOld;
  303. private SceneObjectDiff oldToNew;
  304. private RecordSceneObjectHeaderUndo() { }
  305. /// <summary>
  306. /// Creates the new scene object undo command.
  307. /// </summary>
  308. /// <param name="obj">Scene object on which to apply the performed changes.</param>
  309. /// <param name="fieldPath">
  310. /// Optional path that controls which is the field being modified and should receive input focus when the command
  311. /// is executed. Note that the diffs applied have no restriction on how many fields they can modify at once, but
  312. /// only one field will receive focus.</param>
  313. /// <param name="oldToNew">
  314. /// Difference that can be applied to the old object in order to get the new object state.
  315. /// </param>
  316. /// <param name="newToOld">
  317. /// Difference that can be applied to the new object in order to get the old object state.
  318. /// </param>
  319. public RecordSceneObjectHeaderUndo(SceneObject obj, string fieldPath, SceneObjectDiff oldToNew,
  320. SceneObjectDiff newToOld)
  321. {
  322. this.obj = obj;
  323. this.fieldPath = fieldPath;
  324. this.oldToNew = oldToNew;
  325. this.newToOld = newToOld;
  326. }
  327. /// <inheritdoc/>
  328. protected override void Commit()
  329. {
  330. if (obj == null)
  331. return;
  332. if (obj.IsDestroyed)
  333. {
  334. Debug.LogWarning("Attempting to commit state on a destroyed game-object.");
  335. return;
  336. }
  337. oldToNew.Apply(obj);
  338. FocusOnField();
  339. RefreshInspector();
  340. }
  341. /// <inheritdoc/>
  342. protected override void Revert()
  343. {
  344. if (obj == null)
  345. return;
  346. if (obj.IsDestroyed)
  347. {
  348. Debug.LogWarning("Attempting to revert state on a destroyed game-object.");
  349. return;
  350. }
  351. newToOld.Apply(obj);
  352. FocusOnField();
  353. RefreshInspector();
  354. }
  355. /// <summary>
  356. /// Selects the component's scene object and focuses on the specific field in the inspector, if the inspector
  357. /// window is open.
  358. /// </summary>
  359. private void FocusOnField()
  360. {
  361. if (obj != null)
  362. {
  363. if (Selection.SceneObject != obj)
  364. Selection.SceneObject = obj;
  365. if (!string.IsNullOrEmpty(fieldPath))
  366. {
  367. InspectorWindow inspectorWindow = EditorWindow.GetWindow<InspectorWindow>();
  368. inspectorWindow?.FocusOnField(obj.UUID, fieldPath);
  369. }
  370. }
  371. }
  372. /// <summary>
  373. /// Updates the values of the fields displayed in the inspector window.
  374. /// </summary>
  375. private void RefreshInspector()
  376. {
  377. InspectorWindow inspectorWindow = EditorWindow.GetWindow<InspectorWindow>();
  378. inspectorWindow?.RefreshSceneObjectFields(true);
  379. }
  380. }
  381. /// <summary>
  382. /// Stores the field changes in a <see cref="SceneObject"/> as a difference between two states. Allows those changes to
  383. /// be reverted and re-applied. Unlike <see cref="bs.Editor.RecordSceneObjectHeaderUndo"/> this command records the
  384. /// full scene object state, including changes to its components and optionally any child objects.
  385. /// </summary>
  386. [SerializeObject]
  387. internal class RecordSceneObjectUndo : UndoableCommand
  388. {
  389. private SceneObject obj;
  390. private SerializedSceneObject oldState;
  391. private SerializedSceneObject newState;
  392. private RecordSceneObjectUndo() { }
  393. /// <summary>
  394. /// Creates the new scene object undo command.
  395. /// </summary>
  396. /// <param name="obj">Scene object on which to apply the performed changes.</param>
  397. /// <param name="oldState">Recorded original state of the scene object(s).</param>
  398. /// <param name="newState">Recorded new state of the scene object(s).</param>
  399. /// <param name="description">
  400. /// Optional description of the changes made to the scene object between the two states.
  401. /// </param>
  402. public RecordSceneObjectUndo(SceneObject obj, SerializedSceneObject oldState, SerializedSceneObject newState,
  403. string description)
  404. {
  405. this.obj = obj;
  406. this.oldState = oldState;
  407. this.newState = newState;
  408. }
  409. /// <inheritdoc/>
  410. protected override void Commit()
  411. {
  412. newState?.Restore();
  413. FocusOnObject();
  414. RefreshInspector();
  415. }
  416. /// <inheritdoc/>
  417. protected override void Revert()
  418. {
  419. oldState?.Restore();
  420. FocusOnObject();
  421. RefreshInspector();
  422. }
  423. /// <summary>
  424. /// Selects the scene object if not already selected.
  425. /// </summary>
  426. private void FocusOnObject()
  427. {
  428. if (obj != null)
  429. {
  430. if (Selection.SceneObject != obj)
  431. Selection.SceneObject = obj;
  432. }
  433. }
  434. /// <summary>
  435. /// Updates the values of the fields displayed in the inspector window.
  436. /// </summary>
  437. private void RefreshInspector()
  438. {
  439. InspectorWindow inspectorWindow = EditorWindow.GetWindow<InspectorWindow>();
  440. inspectorWindow?.RefreshSceneObjectFields(true);
  441. }
  442. }
  443. /// <summary>
  444. /// Stores the field changes in a <see cref="Component"/> as a difference between two states. Allows those changes to
  445. /// be reverted and re-applied.
  446. /// </summary>
  447. [SerializeObject]
  448. internal class RecordComponentUndo : UndoableCommand
  449. {
  450. private Component obj;
  451. private string fieldPath;
  452. private SerializedDiff newToOld;
  453. private SerializedDiff oldToNew;
  454. private RecordComponentUndo() { }
  455. /// <summary>
  456. /// Creates the new component undo command.
  457. /// </summary>
  458. /// <param name="obj">Component on which to apply the performed changes.</param>
  459. /// <param name="fieldPath">
  460. /// Optional path that controls which is the field being modified and should receive input focus when the command
  461. /// is executed. Note that the diffs applied have no restriction on how many fields they can modify at once, but
  462. /// only one field will receive focus.</param>
  463. /// <param name="oldToNew">
  464. /// Difference that can be applied to the old object in order to get the new object state.
  465. /// </param>
  466. /// <param name="newToOld">
  467. /// Difference that can be applied to the new object in order to get the old object state.
  468. /// </param>
  469. public RecordComponentUndo(Component obj, string fieldPath, SerializedDiff oldToNew, SerializedDiff newToOld)
  470. {
  471. this.obj = obj;
  472. this.fieldPath = fieldPath;
  473. this.oldToNew = oldToNew;
  474. this.newToOld = newToOld;
  475. }
  476. /// <inheritdoc/>
  477. protected override void Commit()
  478. {
  479. if (oldToNew == null || obj == null)
  480. return;
  481. if (obj.IsDestroyed)
  482. {
  483. Debug.LogWarning("Attempting to commit state on a destroyed game-object.");
  484. return;
  485. }
  486. oldToNew.Apply(obj);
  487. FocusOnField();
  488. }
  489. /// <inheritdoc/>
  490. protected override void Revert()
  491. {
  492. if (newToOld == null || obj == null)
  493. return;
  494. if (obj.IsDestroyed)
  495. {
  496. Debug.LogWarning("Attempting to revert state on a destroyed game-object.");
  497. return;
  498. }
  499. newToOld.Apply(obj);
  500. FocusOnField();
  501. }
  502. /// <summary>
  503. /// Selects the component's scene object and focuses on the specific field in the inspector, if the inspector
  504. /// window is open.
  505. /// </summary>
  506. private void FocusOnField()
  507. {
  508. SceneObject so = obj.SceneObject;
  509. if (so != null)
  510. {
  511. if (Selection.SceneObject != so)
  512. Selection.SceneObject = so;
  513. if (!string.IsNullOrEmpty(fieldPath))
  514. {
  515. InspectorWindow inspectorWindow = EditorWindow.GetWindow<InspectorWindow>();
  516. inspectorWindow?.FocusOnField(obj.UUID, fieldPath);
  517. }
  518. }
  519. }
  520. }
  521. /** @} */
  522. }