AnimationWindow.cs 62 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650
  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.IO;
  6. using System.Text;
  7. using BansheeEngine;
  8. namespace BansheeEditor
  9. {
  10. /** @addtogroup Windows
  11. * @{
  12. */
  13. /// <summary>
  14. /// Displays animation curve editor window. Allows the user to manipulate keyframes of animation curves, add/remove
  15. /// curves from an animation clip, and manipulate animation events.
  16. /// </summary>
  17. [DefaultSize(900, 500)]
  18. internal class AnimationWindow : EditorWindow
  19. {
  20. private const int FIELD_DISPLAY_WIDTH = 300;
  21. private const int DRAG_START_DISTANCE = 3;
  22. private const float DRAG_SCALE = 3.0f;
  23. private const float ZOOM_SCALE = 0.1f/120.0f; // One scroll step is usually 120 units, we want 1/10 of that
  24. private SceneObject selectedSO;
  25. /// <summary>
  26. /// Scene object for which are we currently changing the animation for.
  27. /// </summary>
  28. internal SceneObject SelectedSO
  29. {
  30. get { return selectedSO; }
  31. }
  32. #region Overrides
  33. /// <summary>
  34. /// Opens the animation window.
  35. /// </summary>
  36. [MenuItem("Windows/Animation", ButtonModifier.CtrlAlt, ButtonCode.A, 6000)]
  37. private static void OpenGameWindow()
  38. {
  39. OpenWindow<AnimationWindow>();
  40. }
  41. /// <inheritdoc/>
  42. protected override LocString GetDisplayName()
  43. {
  44. return new LocEdString("Animation");
  45. }
  46. private void OnInitialize()
  47. {
  48. Selection.OnSelectionChanged += OnSelectionChanged;
  49. UpdateSelectedSO(true);
  50. }
  51. private void OnEditorUpdate()
  52. {
  53. if (selectedSO == null)
  54. return;
  55. HandleDragAndZoomInput();
  56. if (state == State.Playback)
  57. {
  58. Animation animation = selectedSO.GetComponent<Animation>();
  59. if (animation != null)
  60. {
  61. float time = animation.EditorGetTime();
  62. int frameIdx = (int)(time * fps);
  63. SetCurrentFrame(frameIdx);
  64. animation.UpdateFloatProperties();
  65. }
  66. }
  67. }
  68. private void OnDestroy()
  69. {
  70. SwitchState(State.Normal);
  71. Selection.OnSelectionChanged -= OnSelectionChanged;
  72. if (selectedSO != null)
  73. {
  74. EditorInput.OnPointerPressed -= OnPointerPressed;
  75. EditorInput.OnPointerMoved -= OnPointerMoved;
  76. EditorInput.OnPointerReleased -= OnPointerReleased;
  77. EditorInput.OnButtonUp -= OnButtonUp;
  78. }
  79. }
  80. /// <inheritdoc/>
  81. protected override void WindowResized(int width, int height)
  82. {
  83. if (selectedSO == null)
  84. return;
  85. ResizeGUI(width, height);
  86. }
  87. #endregion
  88. #region GUI
  89. private GUIToggle playButton;
  90. private GUIToggle recordButton;
  91. private GUIButton prevFrameButton;
  92. private GUIIntField frameInputField;
  93. private GUIButton nextFrameButton;
  94. private GUIButton addKeyframeButton;
  95. private GUIButton addEventButton;
  96. private GUIButton optionsButton;
  97. private GUIButton addPropertyBtn;
  98. private GUIButton delPropertyBtn;
  99. private GUILayout buttonLayout;
  100. private int buttonLayoutHeight;
  101. private int scrollBarWidth;
  102. private int scrollBarHeight;
  103. private GUIResizeableScrollBarH horzScrollBar;
  104. private GUIResizeableScrollBarV vertScrollBar;
  105. private GUIPanel editorPanel;
  106. private GUIAnimFieldDisplay guiFieldDisplay;
  107. private GUICurveEditor guiCurveEditor;
  108. /// <summary>
  109. /// Recreates the entire curve editor GUI depending on the currently selected scene object.
  110. /// </summary>
  111. private void RebuildGUI()
  112. {
  113. GUI.Clear();
  114. guiCurveEditor = null;
  115. guiFieldDisplay = null;
  116. if (selectedSO == null)
  117. {
  118. GUILabel warningLbl = new GUILabel(new LocEdString("Select an object to animate in the Hierarchy or Scene windows."));
  119. GUILayoutY vertLayout = GUI.AddLayoutY();
  120. vertLayout.AddFlexibleSpace();
  121. GUILayoutX horzLayout = vertLayout.AddLayoutX();
  122. vertLayout.AddFlexibleSpace();
  123. horzLayout.AddFlexibleSpace();
  124. horzLayout.AddElement(warningLbl);
  125. horzLayout.AddFlexibleSpace();
  126. return;
  127. }
  128. // Top button row
  129. GUIContent playIcon = new GUIContent(EditorBuiltin.GetAnimationWindowIcon(AnimationWindowIcon.Play),
  130. new LocEdString("Play"));
  131. GUIContent recordIcon = new GUIContent(EditorBuiltin.GetAnimationWindowIcon(AnimationWindowIcon.Record),
  132. new LocEdString("Record"));
  133. GUIContent prevFrameIcon = new GUIContent(EditorBuiltin.GetAnimationWindowIcon(AnimationWindowIcon.FrameBack),
  134. new LocEdString("Previous frame"));
  135. GUIContent nextFrameIcon = new GUIContent(EditorBuiltin.GetAnimationWindowIcon(AnimationWindowIcon.FrameForward),
  136. new LocEdString("Next frame"));
  137. GUIContent addKeyframeIcon = new GUIContent(EditorBuiltin.GetAnimationWindowIcon(AnimationWindowIcon.AddKeyframe),
  138. new LocEdString("Add keyframe"));
  139. GUIContent addEventIcon = new GUIContent(EditorBuiltin.GetAnimationWindowIcon(AnimationWindowIcon.AddEvent),
  140. new LocEdString("Add event"));
  141. GUIContent optionsIcon = new GUIContent(EditorBuiltin.GetLibraryWindowIcon(LibraryWindowIcon.Options),
  142. new LocEdString("Options"));
  143. playButton = new GUIToggle(playIcon, EditorStyles.Button);
  144. recordButton = new GUIToggle(recordIcon, EditorStyles.Button);
  145. prevFrameButton = new GUIButton(prevFrameIcon);
  146. frameInputField = new GUIIntField();
  147. nextFrameButton = new GUIButton(nextFrameIcon);
  148. addKeyframeButton = new GUIButton(addKeyframeIcon);
  149. addEventButton = new GUIButton(addEventIcon);
  150. optionsButton = new GUIButton(optionsIcon);
  151. playButton.OnToggled += x =>
  152. {
  153. if(x)
  154. SwitchState(State.Playback);
  155. else
  156. SwitchState(State.Normal);
  157. };
  158. recordButton.OnToggled += x =>
  159. {
  160. if (x)
  161. SwitchState(State.Recording);
  162. else
  163. SwitchState(State.Normal);
  164. };
  165. prevFrameButton.OnClick += () =>
  166. {
  167. SwitchState(State.Normal);
  168. SetCurrentFrame(currentFrameIdx - 1);
  169. };
  170. frameInputField.OnChanged += x =>
  171. {
  172. SwitchState(State.Normal);
  173. SetCurrentFrame(x);
  174. };
  175. nextFrameButton.OnClick += () =>
  176. {
  177. SwitchState(State.Normal);
  178. SetCurrentFrame(currentFrameIdx + 1);
  179. };
  180. addKeyframeButton.OnClick += () =>
  181. {
  182. SwitchState(State.Normal);
  183. guiCurveEditor.AddKeyFrameAtMarker();
  184. };
  185. addEventButton.OnClick += () =>
  186. {
  187. SwitchState(State.Normal);
  188. guiCurveEditor.AddEventAtMarker();
  189. };
  190. optionsButton.OnClick += () =>
  191. {
  192. Vector2I openPosition = ScreenToWindowPos(Input.PointerPosition);
  193. AnimationOptions dropDown = DropDownWindow.Open<AnimationOptions>(this, openPosition);
  194. dropDown.Initialize(this);
  195. };
  196. // Property buttons
  197. addPropertyBtn = new GUIButton(new LocEdString("Add property"));
  198. delPropertyBtn = new GUIButton(new LocEdString("Delete selected"));
  199. addPropertyBtn.OnClick += () =>
  200. {
  201. Action openPropertyWindow = () =>
  202. {
  203. Vector2I windowPos = ScreenToWindowPos(Input.PointerPosition);
  204. FieldSelectionWindow fieldSelection = DropDownWindow.Open<FieldSelectionWindow>(this, windowPos);
  205. fieldSelection.OnFieldSelected += OnFieldAdded;
  206. };
  207. SwitchState(State.Normal);
  208. if (clipInfo.clip == null)
  209. {
  210. LocEdString title = new LocEdString("Warning");
  211. LocEdString message =
  212. new LocEdString("Selected object doesn't have an animation clip assigned. Would you like to create" +
  213. " a new animation clip?");
  214. DialogBox.Open(title, message, DialogBox.Type.YesNoCancel, type =>
  215. {
  216. if (type == DialogBox.ResultType.Yes)
  217. {
  218. string clipSavePath;
  219. if (BrowseDialog.SaveFile(ProjectLibrary.ResourceFolder, "*.asset", out clipSavePath))
  220. {
  221. clipSavePath = Path.ChangeExtension(clipSavePath, ".asset");
  222. AnimationClip newClip = new AnimationClip();
  223. ProjectLibrary.Create(newClip, clipSavePath);
  224. LoadAnimClip(newClip);
  225. Animation animation = selectedSO.GetComponent<Animation>();
  226. if (animation == null)
  227. animation = selectedSO.AddComponent<Animation>();
  228. animation.DefaultClip = newClip;
  229. EditorApplication.SetSceneDirty();
  230. openPropertyWindow();
  231. }
  232. }
  233. });
  234. }
  235. else
  236. {
  237. if (clipInfo.isImported)
  238. {
  239. LocEdString title = new LocEdString("Warning");
  240. LocEdString message =
  241. new LocEdString("You cannot add/edit/remove curves from animation clips that" +
  242. " are imported from an external file.");
  243. DialogBox.Open(title, message, DialogBox.Type.OK);
  244. }
  245. else
  246. openPropertyWindow();
  247. }
  248. };
  249. delPropertyBtn.OnClick += () =>
  250. {
  251. if (clipInfo.clip == null)
  252. return;
  253. SwitchState(State.Normal);
  254. if (clipInfo.isImported)
  255. {
  256. LocEdString title = new LocEdString("Warning");
  257. LocEdString message =
  258. new LocEdString("You cannot add/edit/remove curves from animation clips that" +
  259. " are imported from an external file.");
  260. DialogBox.Open(title, message, DialogBox.Type.OK);
  261. }
  262. else
  263. {
  264. LocEdString title = new LocEdString("Warning");
  265. LocEdString message = new LocEdString("Are you sure you want to remove all selected fields?");
  266. DialogBox.Open(title, message, DialogBox.Type.YesNo, x =>
  267. {
  268. if (x == DialogBox.ResultType.Yes)
  269. {
  270. RemoveSelectedFields();
  271. ApplyClipChanges();
  272. }
  273. });
  274. }
  275. };
  276. GUIPanel mainPanel = GUI.AddPanel();
  277. GUIPanel backgroundPanel = GUI.AddPanel(1);
  278. GUILayout mainLayout = mainPanel.AddLayoutY();
  279. buttonLayout = mainLayout.AddLayoutX();
  280. buttonLayout.AddSpace(5);
  281. buttonLayout.AddElement(playButton);
  282. buttonLayout.AddElement(recordButton);
  283. buttonLayout.AddSpace(5);
  284. buttonLayout.AddElement(prevFrameButton);
  285. buttonLayout.AddElement(frameInputField);
  286. buttonLayout.AddElement(nextFrameButton);
  287. buttonLayout.AddSpace(5);
  288. buttonLayout.AddElement(addKeyframeButton);
  289. buttonLayout.AddElement(addEventButton);
  290. buttonLayout.AddSpace(5);
  291. buttonLayout.AddElement(optionsButton);
  292. buttonLayout.AddFlexibleSpace();
  293. buttonLayoutHeight = playButton.Bounds.height;
  294. GUITexture buttonBackground = new GUITexture(null, EditorStyles.HeaderBackground);
  295. buttonBackground.Bounds = new Rect2I(0, 0, Width, buttonLayoutHeight);
  296. backgroundPanel.AddElement(buttonBackground);
  297. GUILayout contentLayout = mainLayout.AddLayoutX();
  298. GUILayout fieldDisplayLayout = contentLayout.AddLayoutY(GUIOption.FixedWidth(FIELD_DISPLAY_WIDTH));
  299. guiFieldDisplay = new GUIAnimFieldDisplay(fieldDisplayLayout, FIELD_DISPLAY_WIDTH,
  300. Height - buttonLayoutHeight * 2, selectedSO);
  301. guiFieldDisplay.OnEntrySelected += OnFieldSelected;
  302. GUILayout bottomButtonLayout = fieldDisplayLayout.AddLayoutX();
  303. bottomButtonLayout.AddElement(addPropertyBtn);
  304. bottomButtonLayout.AddElement(delPropertyBtn);
  305. horzScrollBar = new GUIResizeableScrollBarH();
  306. horzScrollBar.OnScrollOrResize += OnHorzScrollOrResize;
  307. vertScrollBar = new GUIResizeableScrollBarV();
  308. vertScrollBar.OnScrollOrResize += OnVertScrollOrResize;
  309. GUITexture separator = new GUITexture(null, EditorStyles.Separator, GUIOption.FixedWidth(3));
  310. contentLayout.AddElement(separator);
  311. GUILayout curveLayout = contentLayout.AddLayoutY();
  312. GUILayout curveLayoutHorz = curveLayout.AddLayoutX();
  313. GUILayout horzScrollBarLayout = curveLayout.AddLayoutX();
  314. horzScrollBarLayout.AddElement(horzScrollBar);
  315. horzScrollBarLayout.AddFlexibleSpace();
  316. editorPanel = curveLayoutHorz.AddPanel();
  317. curveLayoutHorz.AddElement(vertScrollBar);
  318. curveLayoutHorz.AddFlexibleSpace();
  319. scrollBarHeight = horzScrollBar.Bounds.height;
  320. scrollBarWidth = vertScrollBar.Bounds.width;
  321. Vector2I curveEditorSize = GetCurveEditorSize();
  322. guiCurveEditor = new GUICurveEditor(this, editorPanel, curveEditorSize.x, curveEditorSize.y);
  323. guiCurveEditor.OnFrameSelected += OnFrameSelected;
  324. guiCurveEditor.OnEventAdded += OnEventsChanged;
  325. guiCurveEditor.OnEventModified += EditorApplication.SetProjectDirty;
  326. guiCurveEditor.OnEventDeleted += OnEventsChanged;
  327. guiCurveEditor.OnCurveModified += () =>
  328. {
  329. ApplyClipChanges();
  330. EditorApplication.SetProjectDirty();
  331. };
  332. guiCurveEditor.OnClicked += () => SwitchState(State.Normal);
  333. guiCurveEditor.Redraw();
  334. horzScrollBar.SetWidth(curveEditorSize.x);
  335. vertScrollBar.SetHeight(curveEditorSize.y);
  336. UpdateScrollBarSize();
  337. }
  338. /// <summary>
  339. /// Resizes GUI elements so they fit within the provided boundaries.
  340. /// </summary>
  341. /// <param name="width">Width of the GUI bounds, in pixels.</param>
  342. /// <param name="height">Height of the GUI bounds, in pixels.</param>
  343. private void ResizeGUI(int width, int height)
  344. {
  345. guiFieldDisplay.SetSize(FIELD_DISPLAY_WIDTH, height - buttonLayoutHeight * 2);
  346. Vector2I curveEditorSize = GetCurveEditorSize();
  347. guiCurveEditor.SetSize(curveEditorSize.x, curveEditorSize.y);
  348. guiCurveEditor.Redraw();
  349. horzScrollBar.SetWidth(curveEditorSize.x);
  350. vertScrollBar.SetHeight(curveEditorSize.y);
  351. UpdateScrollBarSize();
  352. UpdateScrollBarPosition();
  353. }
  354. #endregion
  355. #region Scroll, drag, zoom
  356. private Vector2I dragStartPos;
  357. private bool isButtonHeld;
  358. private bool isDragInProgress;
  359. private float zoomAmount;
  360. /// <summary>
  361. /// Handles mouse scroll wheel and dragging events in order to zoom or drag the displayed curve editor contents.
  362. /// </summary>
  363. private void HandleDragAndZoomInput()
  364. {
  365. // Handle middle mouse dragging
  366. if (isDragInProgress)
  367. {
  368. float lengthPerPixel = guiCurveEditor.Range.x / guiCurveEditor.Width;
  369. float heightPerPixel = guiCurveEditor.Range.y / guiCurveEditor.Height;
  370. float dragX = Input.GetAxisValue(InputAxis.MouseX) * DRAG_SCALE * lengthPerPixel;
  371. float dragY = Input.GetAxisValue(InputAxis.MouseY) * DRAG_SCALE * heightPerPixel;
  372. Vector2 offset = guiCurveEditor.Offset;
  373. offset.x = Math.Max(0.0f, offset.x + dragX);
  374. offset.y -= dragY;
  375. guiCurveEditor.Offset = offset;
  376. UpdateScrollBarSize();
  377. UpdateScrollBarPosition();
  378. }
  379. // Handle zoom in/out
  380. float scroll = Input.GetAxisValue(InputAxis.MouseZ);
  381. if (scroll != 0.0f)
  382. {
  383. Vector2I windowPos = ScreenToWindowPos(Input.PointerPosition);
  384. Vector2 curvePos;
  385. if (guiCurveEditor.WindowToCurveSpace(windowPos, out curvePos))
  386. {
  387. float zoom = scroll * ZOOM_SCALE;
  388. Zoom(curvePos, zoom);
  389. }
  390. }
  391. }
  392. /// <summary>
  393. /// Moves or resizes the vertical scroll bar under the curve editor.
  394. /// </summary>
  395. /// <param name="position">New position of the scrollbar, in range [0, 1].</param>
  396. /// <param name="size">New size of the scrollbar handle, in range [0, 1].</param>
  397. private void SetVertScrollbarProperties(float position, float size)
  398. {
  399. Vector2 visibleRange = guiCurveEditor.Range;
  400. Vector2 totalRange = GetTotalRange();
  401. visibleRange.y = totalRange.y*size;
  402. guiCurveEditor.Range = visibleRange;
  403. float scrollableRange = totalRange.y - visibleRange.y;
  404. Vector2 offset = guiCurveEditor.Offset;
  405. offset.y = -scrollableRange * (position * 2.0f - 1.0f);
  406. guiCurveEditor.Offset = offset;
  407. }
  408. /// <summary>
  409. /// Moves or resizes the horizontal scroll bar under the curve editor.
  410. /// </summary>
  411. /// <param name="position">New position of the scrollbar, in range [0, 1].</param>
  412. /// <param name="size">New size of the scrollbar handle, in range [0, 1].</param>
  413. private void SetHorzScrollbarProperties(float position, float size)
  414. {
  415. Vector2 visibleRange = guiCurveEditor.Range;
  416. Vector2 totalRange = GetTotalRange();
  417. visibleRange.x = totalRange.x * size;
  418. guiCurveEditor.Range = visibleRange;
  419. float scrollableRange = totalRange.x - visibleRange.x;
  420. Vector2 offset = guiCurveEditor.Offset;
  421. offset.x = scrollableRange * position;
  422. guiCurveEditor.Offset = offset;
  423. }
  424. /// <summary>
  425. /// Updates the size of both scrollbars depending on the currently visible curve area vs. the total curve area.
  426. /// </summary>
  427. private void UpdateScrollBarSize()
  428. {
  429. Vector2 visibleRange = guiCurveEditor.Range;
  430. Vector2 totalRange = GetTotalRange();
  431. horzScrollBar.HandleSize = visibleRange.x / totalRange.x;
  432. vertScrollBar.HandleSize = visibleRange.y / totalRange.y;
  433. }
  434. /// <summary>
  435. /// Updates the position of both scrollbars depending on the offset currently applied to the visible curve area.
  436. /// </summary>
  437. private void UpdateScrollBarPosition()
  438. {
  439. Vector2 visibleRange = guiCurveEditor.Range;
  440. Vector2 totalRange = GetTotalRange();
  441. Vector2 scrollableRange = totalRange - visibleRange;
  442. Vector2 offset = guiCurveEditor.Offset;
  443. if (scrollableRange.x > 0.0f)
  444. horzScrollBar.Position = offset.x / scrollableRange.x;
  445. else
  446. horzScrollBar.Position = 0.0f;
  447. if (scrollableRange.y > 0.0f)
  448. {
  449. float pos = offset.y/scrollableRange.y;
  450. float sign = MathEx.Sign(pos);
  451. pos = sign*MathEx.Clamp01(MathEx.Abs(pos));
  452. pos = (1.0f - pos) /2.0f;
  453. vertScrollBar.Position = pos;
  454. }
  455. else
  456. vertScrollBar.Position = 0.0f;
  457. }
  458. /// <summary>
  459. /// Calculates the width/height of the curve area depending on the current zoom level.
  460. /// </summary>
  461. /// <returns>Width/height of the curve area, in curve space (value, time).</returns>
  462. private Vector2 GetZoomedRange()
  463. {
  464. float zoomLevel = MathEx.Pow(2, zoomAmount);
  465. Vector2 optimalRange = GetOptimalRange();
  466. return optimalRange / zoomLevel;
  467. }
  468. /// <summary>
  469. /// Returns the total width/height of the contents of the curve area.
  470. /// </summary>
  471. /// <returns>Width/height of the curve area, in curve space (value, time).</returns>
  472. private Vector2 GetTotalRange()
  473. {
  474. // Return optimal range (that covers the visible curve)
  475. Vector2 optimalRange = GetOptimalRange();
  476. // Increase range in case user zoomed out
  477. Vector2 zoomedRange = GetZoomedRange();
  478. return Vector2.Max(optimalRange, zoomedRange);
  479. }
  480. /// <summary>
  481. /// Zooms in or out at the provided position in the curve display.
  482. /// </summary>
  483. /// <param name="curvePos">Position to zoom towards, relative to the curve display area, in curve space
  484. /// (value, time)</param>
  485. /// <param name="amount">Amount to zoom in (positive), or out (negative).</param>
  486. private void Zoom(Vector2 curvePos, float amount)
  487. {
  488. // Increase or decrease the visible range depending on zoom level
  489. Vector2 oldZoomedRange = GetZoomedRange();
  490. zoomAmount = MathEx.Clamp(zoomAmount + amount, -10.0f, 10.0f);
  491. Vector2 zoomedRange = GetZoomedRange();
  492. Vector2 zoomedDiff = zoomedRange - oldZoomedRange;
  493. Vector2 currentRange = guiCurveEditor.Range;
  494. Vector2 newRange = currentRange + zoomedDiff;
  495. guiCurveEditor.Range = newRange;
  496. // When zooming, make sure to focus on the point provided, so adjust the offset
  497. Vector2 rangeScale = newRange;
  498. rangeScale.x /= currentRange.x;
  499. rangeScale.y /= currentRange.y;
  500. Vector2 relativeCurvePos = curvePos - guiCurveEditor.Offset;
  501. Vector2 newCurvePos = relativeCurvePos * rangeScale;
  502. Vector2 diff = newCurvePos - relativeCurvePos;
  503. guiCurveEditor.Offset -= diff;
  504. UpdateScrollBarSize();
  505. UpdateScrollBarPosition();
  506. }
  507. #endregion
  508. #region Curve save/load
  509. private EditorAnimClipInfo clipInfo;
  510. /// <summary>
  511. /// Refreshes the contents of the curve and property display by loading animation curves from the provided
  512. /// animation clip.
  513. /// </summary>
  514. /// <param name="clip">Clip containing the animation to load.</param>
  515. private void LoadAnimClip(AnimationClip clip)
  516. {
  517. EditorPersistentData persistentData = EditorApplication.PersistentData;
  518. if (persistentData.dirtyAnimClips.TryGetValue(clip.UUID, out clipInfo))
  519. {
  520. // If an animation clip is imported, we don't care about it's cached curve values as they could have changed
  521. // since last modification, so we re-load the clip. But we persist the events as those can only be set
  522. // within the editor.
  523. if (clipInfo.isImported)
  524. {
  525. EditorAnimClipInfo newClipInfo = EditorAnimClipInfo.Create(clip);
  526. newClipInfo.events = clipInfo.events;
  527. }
  528. }
  529. else
  530. clipInfo = EditorAnimClipInfo.Create(clip);
  531. persistentData.dirtyAnimClips[clip.UUID] = clipInfo;
  532. foreach (var curve in clipInfo.curves)
  533. guiFieldDisplay.AddField(new AnimFieldInfo(curve.Key, curve.Value, !clipInfo.isImported));
  534. guiCurveEditor.Events = clipInfo.events;
  535. guiCurveEditor.DisableCurveEdit = clipInfo.isImported;
  536. SetCurrentFrame(0);
  537. FPS = clipInfo.sampleRate;
  538. }
  539. /// <summary>
  540. /// Applies any changes made to the animation curves and events to the actual animation clip resource.
  541. /// </summary>
  542. private void ApplyClipChanges()
  543. {
  544. EditorAnimClipTangents unused;
  545. clipInfo.Apply(out unused);
  546. }
  547. /// <summary>
  548. /// Checks if the currently selected object has changed, and rebuilds the GUI and loads the animation clip if needed.
  549. /// </summary>
  550. /// <param name="force">If true the GUI rebuild and animation clip load will be forced regardless if the active
  551. /// scene object changed.</param>
  552. private void UpdateSelectedSO(bool force)
  553. {
  554. SceneObject so = Selection.SceneObject;
  555. if (selectedSO != so || force)
  556. {
  557. if (selectedSO != null && so == null)
  558. {
  559. EditorInput.OnPointerPressed -= OnPointerPressed;
  560. EditorInput.OnPointerMoved -= OnPointerMoved;
  561. EditorInput.OnPointerReleased -= OnPointerReleased;
  562. EditorInput.OnButtonUp -= OnButtonUp;
  563. }
  564. else if (selectedSO == null && so != null)
  565. {
  566. EditorInput.OnPointerPressed += OnPointerPressed;
  567. EditorInput.OnPointerMoved += OnPointerMoved;
  568. EditorInput.OnPointerReleased += OnPointerReleased;
  569. EditorInput.OnButtonUp += OnButtonUp;
  570. }
  571. zoomAmount = 0.0f;
  572. selectedSO = so;
  573. selectedFields.Clear();
  574. clipInfo = null;
  575. RebuildGUI();
  576. // Load existing clip if one exists
  577. if (selectedSO != null)
  578. {
  579. Animation animation = selectedSO.GetComponent<Animation>();
  580. if (animation != null)
  581. {
  582. AnimationClip clip = animation.DefaultClip;
  583. if (clip != null)
  584. LoadAnimClip(clip);
  585. }
  586. }
  587. if(clipInfo == null)
  588. clipInfo = new EditorAnimClipInfo();
  589. if(selectedSO != null)
  590. UpdateDisplayedCurves(true);
  591. }
  592. }
  593. #endregion
  594. #region Record/Playback
  595. /// <summary>
  596. /// Possible states the animation window can be in.
  597. /// </summary>
  598. private enum State
  599. {
  600. Normal,
  601. Recording,
  602. Playback
  603. }
  604. private State state = State.Normal;
  605. /// <summary>
  606. /// Transitions the window into a different state. Caller must validate state transitions.
  607. /// </summary>
  608. /// <param name="state">New state to transition to.</param>
  609. private void SwitchState(State state)
  610. {
  611. switch (this.state)
  612. {
  613. case State.Normal:
  614. {
  615. switch (state)
  616. {
  617. case State.Playback:
  618. StartPlayback();
  619. break;
  620. case State.Recording:
  621. StartRecord();
  622. break;
  623. }
  624. }
  625. break;
  626. case State.Playback:
  627. {
  628. switch (state)
  629. {
  630. case State.Normal:
  631. EndPlayback();
  632. break;
  633. case State.Recording:
  634. EndPlayback();
  635. StartRecord();
  636. break;
  637. }
  638. }
  639. break;
  640. case State.Recording:
  641. {
  642. switch (state)
  643. {
  644. case State.Normal:
  645. EndRecord();
  646. break;
  647. case State.Playback:
  648. EndRecord();
  649. StartPlayback();
  650. break;
  651. }
  652. }
  653. break;
  654. }
  655. this.state = state;
  656. }
  657. /// <summary>
  658. /// Plays back the animation on the currently selected object.
  659. /// </summary>
  660. private void StartPlayback()
  661. {
  662. EditorAnimClipTangents unused;
  663. clipInfo.Apply(out unused);
  664. Animation animation = selectedSO.GetComponent<Animation>();
  665. if (animation != null)
  666. {
  667. float time = guiCurveEditor.GetTimeForFrame(currentFrameIdx);
  668. animation.EditorPlay(clipInfo.clip, time);
  669. }
  670. playButton.Value = true;
  671. }
  672. /// <summary>
  673. /// Ends playback started with <see cref="StartPlayback"/>
  674. /// </summary>
  675. private void EndPlayback()
  676. {
  677. Animation animation = selectedSO.GetComponent<Animation>();
  678. if (animation != null)
  679. animation.EditorStop();
  680. playButton.Value = false;
  681. }
  682. /// <summary>
  683. /// Start recording modifications made to the selected scene object.
  684. /// </summary>
  685. private void StartRecord()
  686. {
  687. // TODO
  688. recordButton.Value = true;
  689. }
  690. /// <summary>
  691. /// Stops recording modifications made to the selected scene object.
  692. /// </summary>
  693. private void EndRecord()
  694. {
  695. // TODO
  696. // TODO - Lock selection while active? (Don't allow another object to become anim focus in order to allow modifications on anim children).
  697. recordButton.Value = false;
  698. }
  699. /// <summary>
  700. /// Iterates over all curve path fields and records their current state. If the state differs from the current
  701. /// curve values, new keyframes are added.
  702. /// </summary>
  703. /// <param name="time">Time for which to record the state, in seconds.</param>
  704. private void RecordState(float time)
  705. {
  706. Action<EdAnimationCurve, float, float> addOrUpdateKeyframe = (curve, keyTime, keyValue) =>
  707. {
  708. KeyFrame[] keyframes = curve.KeyFrames;
  709. int keyframeIdx = -1;
  710. for (int i = 0; i < keyframes.Length; i++)
  711. {
  712. if (MathEx.ApproxEquals(keyframes[i].time, time))
  713. {
  714. keyframeIdx = i;
  715. break;
  716. }
  717. }
  718. if (keyframeIdx != -1)
  719. curve.UpdateKeyframe(keyframeIdx, keyTime, keyValue);
  720. else
  721. curve.AddKeyframe(keyTime, keyValue);
  722. curve.Apply();
  723. };
  724. foreach (var KVP in clipInfo.curves)
  725. {
  726. string suffix;
  727. SerializableProperty property = Animation.FindProperty(selectedSO, KVP.Key, out suffix);
  728. if (property == null)
  729. continue;
  730. switch (KVP.Value.type)
  731. {
  732. case SerializableProperty.FieldType.Vector2:
  733. {
  734. Vector2 value = property.GetValue<Vector2>();
  735. for (int i = 0; i < 2; i++)
  736. {
  737. float curveVal = KVP.Value.curveInfos[i].curve.Evaluate(time);
  738. if (!MathEx.ApproxEquals(value[i], curveVal))
  739. addOrUpdateKeyframe(KVP.Value.curveInfos[i].curve, time, curveVal);
  740. }
  741. }
  742. break;
  743. case SerializableProperty.FieldType.Vector3:
  744. {
  745. Vector3 value = property.GetValue<Vector3>();
  746. for (int i = 0; i < 3; i++)
  747. {
  748. float curveVal = KVP.Value.curveInfos[i].curve.Evaluate(time);
  749. if (!MathEx.ApproxEquals(value[i], curveVal))
  750. addOrUpdateKeyframe(KVP.Value.curveInfos[i].curve, time, curveVal);
  751. }
  752. }
  753. break;
  754. case SerializableProperty.FieldType.Vector4:
  755. {
  756. if (property.InternalType == typeof(Vector4))
  757. {
  758. Vector4 value = property.GetValue<Vector4>();
  759. for (int i = 0; i < 4; i++)
  760. {
  761. float curveVal = KVP.Value.curveInfos[i].curve.Evaluate(time);
  762. if (!MathEx.ApproxEquals(value[i], curveVal))
  763. addOrUpdateKeyframe(KVP.Value.curveInfos[i].curve, time, curveVal);
  764. }
  765. }
  766. else if (property.InternalType == typeof(Quaternion))
  767. {
  768. Quaternion value = property.GetValue<Quaternion>();
  769. for (int i = 0; i < 4; i++)
  770. {
  771. float curveVal = KVP.Value.curveInfos[i].curve.Evaluate(time);
  772. if (!MathEx.ApproxEquals(value[i], curveVal))
  773. addOrUpdateKeyframe(KVP.Value.curveInfos[i].curve, time, curveVal);
  774. }
  775. }
  776. }
  777. break;
  778. case SerializableProperty.FieldType.Color:
  779. {
  780. Color value = property.GetValue<Color>();
  781. for (int i = 0; i < 4; i++)
  782. {
  783. float curveVal = KVP.Value.curveInfos[i].curve.Evaluate(time);
  784. if (!MathEx.ApproxEquals(value[i], curveVal))
  785. addOrUpdateKeyframe(KVP.Value.curveInfos[i].curve, time, curveVal);
  786. }
  787. }
  788. break;
  789. case SerializableProperty.FieldType.Bool:
  790. {
  791. bool value = property.GetValue<bool>();
  792. bool curveVal = KVP.Value.curveInfos[0].curve.Evaluate(time) > 0.0f;
  793. if (value != curveVal)
  794. addOrUpdateKeyframe(KVP.Value.curveInfos[0].curve, time, curveVal ? 1.0f : -1.0f);
  795. }
  796. break;
  797. case SerializableProperty.FieldType.Int:
  798. {
  799. int value = property.GetValue<int>();
  800. int curveVal = (int)KVP.Value.curveInfos[0].curve.Evaluate(time);
  801. if (value != curveVal)
  802. addOrUpdateKeyframe(KVP.Value.curveInfos[0].curve, time, curveVal);
  803. }
  804. break;
  805. case SerializableProperty.FieldType.Float:
  806. {
  807. float value = property.GetValue<float>();
  808. float curveVal = KVP.Value.curveInfos[0].curve.Evaluate(time);
  809. if (!MathEx.ApproxEquals(value, curveVal))
  810. addOrUpdateKeyframe(KVP.Value.curveInfos[0].curve, time, curveVal);
  811. }
  812. break;
  813. }
  814. }
  815. }
  816. #endregion
  817. #region Curve display
  818. private int currentFrameIdx;
  819. private int fps = 1;
  820. /// <summary>
  821. /// Sampling rate of the animation in frames per second. Determines granularity at which positions keyframes can be
  822. /// placed.
  823. /// </summary>
  824. internal int FPS
  825. {
  826. get { return fps; }
  827. set { guiCurveEditor.SetFPS(value); fps = MathEx.Max(value, 1); }
  828. }
  829. /// <summary>
  830. /// Changes the currently selected frame in the curve display.
  831. /// </summary>
  832. /// <param name="frameIdx">Index of the frame to select.</param>
  833. private void SetCurrentFrame(int frameIdx)
  834. {
  835. currentFrameIdx = Math.Max(0, frameIdx);
  836. frameInputField.Value = currentFrameIdx;
  837. guiCurveEditor.SetMarkedFrame(currentFrameIdx);
  838. float time = guiCurveEditor.GetTimeForFrame(currentFrameIdx);
  839. List<GUIAnimFieldPathValue> values = new List<GUIAnimFieldPathValue>();
  840. foreach (var kvp in clipInfo.curves)
  841. {
  842. GUIAnimFieldPathValue fieldValue = new GUIAnimFieldPathValue();
  843. fieldValue.path = kvp.Key;
  844. switch (kvp.Value.type)
  845. {
  846. case SerializableProperty.FieldType.Vector2:
  847. {
  848. Vector2 value = new Vector2();
  849. for (int i = 0; i < 2; i++)
  850. value[i] = kvp.Value.curveInfos[i].curve.Evaluate(time, false);
  851. fieldValue.value = value;
  852. }
  853. break;
  854. case SerializableProperty.FieldType.Vector3:
  855. {
  856. Vector3 value = new Vector3();
  857. for (int i = 0; i < 3; i++)
  858. value[i] = kvp.Value.curveInfos[i].curve.Evaluate(time, false);
  859. fieldValue.value = value;
  860. }
  861. break;
  862. case SerializableProperty.FieldType.Vector4:
  863. {
  864. Vector4 value = new Vector4();
  865. for (int i = 0; i < 4; i++)
  866. value[i] = kvp.Value.curveInfos[i].curve.Evaluate(time, false);
  867. fieldValue.value = value;
  868. }
  869. break;
  870. case SerializableProperty.FieldType.Color:
  871. {
  872. Color value = new Color();
  873. for (int i = 0; i < 4; i++)
  874. value[i] = kvp.Value.curveInfos[i].curve.Evaluate(time, false);
  875. fieldValue.value = value;
  876. }
  877. break;
  878. case SerializableProperty.FieldType.Bool:
  879. case SerializableProperty.FieldType.Int:
  880. case SerializableProperty.FieldType.Float:
  881. fieldValue.value = kvp.Value.curveInfos[0].curve.Evaluate(time, false); ;
  882. break;
  883. }
  884. values.Add(fieldValue);
  885. }
  886. guiFieldDisplay.SetDisplayValues(values.ToArray());
  887. }
  888. /// <summary>
  889. /// Returns a list of all animation curves that should be displayed in the curve display.
  890. /// </summary>
  891. /// <returns>Array of curves to display.</returns>
  892. private CurveDrawInfo[] GetDisplayedCurves()
  893. {
  894. List<CurveDrawInfo> curvesToDisplay = new List<CurveDrawInfo>();
  895. if (selectedFields.Count == 0) // Display all if nothing is selected
  896. {
  897. if (clipInfo == null)
  898. return curvesToDisplay.ToArray();
  899. foreach (var curve in clipInfo.curves)
  900. {
  901. for (int i = 0; i < curve.Value.curveInfos.Length; i++)
  902. curvesToDisplay.Add(curve.Value.curveInfos[i]);
  903. }
  904. }
  905. else
  906. {
  907. for (int i = 0; i < selectedFields.Count; i++)
  908. {
  909. CurveDrawInfo[] curveInfos;
  910. if (TryGetCurve(selectedFields[i], out curveInfos))
  911. curvesToDisplay.AddRange(curveInfos);
  912. }
  913. }
  914. return curvesToDisplay.ToArray();
  915. }
  916. /// <summary>
  917. /// Returns width/height required to show the entire contents of the currently displayed curves.
  918. /// </summary>
  919. /// <returns>Width/height of the curve area, in curve space (value, time).</returns>
  920. private Vector2 GetOptimalRange()
  921. {
  922. CurveDrawInfo[] curvesToDisplay = GetDisplayedCurves();
  923. float xRange;
  924. float yRange;
  925. CalculateRange(curvesToDisplay, out xRange, out yRange);
  926. // Add padding to y range
  927. yRange *= 1.05f;
  928. // Don't allow zero range
  929. if (xRange == 0.0f)
  930. xRange = 60.0f;
  931. if (yRange == 0.0f)
  932. yRange = 10.0f;
  933. return new Vector2(xRange, yRange);
  934. }
  935. /// <summary>
  936. /// Calculates an unique color for each animation curve.
  937. /// </summary>
  938. private void UpdateCurveColors()
  939. {
  940. int globalCurveIdx = 0;
  941. foreach (var curveGroup in clipInfo.curves)
  942. {
  943. for (int i = 0; i < curveGroup.Value.curveInfos.Length; i++)
  944. curveGroup.Value.curveInfos[i].color = GUICurveDrawing.GetUniqueColor(globalCurveIdx++);
  945. }
  946. }
  947. /// <summary>
  948. /// Updates the curve display with currently selected curves.
  949. /// </summary>
  950. /// <param name="allowReduce">Normally the curve display will expand if newly selected curves cover a larger area
  951. /// than currently available, but the area won't be reduced if the selected curves cover
  952. /// a smaller area. Set this to true to allow the area to be reduced.</param>
  953. private void UpdateDisplayedCurves(bool allowReduce = false)
  954. {
  955. CurveDrawInfo[] curvesToDisplay = GetDisplayedCurves();
  956. guiCurveEditor.SetCurves(curvesToDisplay);
  957. Vector2 newRange = GetOptimalRange();
  958. if (!allowReduce)
  959. {
  960. // Don't reduce visible range
  961. newRange.x = Math.Max(newRange.x, guiCurveEditor.Range.x);
  962. newRange.y = Math.Max(newRange.y, guiCurveEditor.Range.y);
  963. }
  964. guiCurveEditor.Range = newRange;
  965. UpdateScrollBarSize();
  966. }
  967. #endregion
  968. #region Field display
  969. private List<string> selectedFields = new List<string>();
  970. /// <summary>
  971. /// Registers a new animation curve field.
  972. /// </summary>
  973. /// <param name="path">Path of the field, see <see cref="GUIFieldSelector.OnElementSelected"/></param>
  974. /// <param name="type">Type of the field (float, vector, etc.)</param>
  975. private void AddNewField(string path, SerializableProperty.FieldType type)
  976. {
  977. bool noSelection = selectedFields.Count == 0;
  978. switch (type)
  979. {
  980. case SerializableProperty.FieldType.Vector4:
  981. {
  982. FieldAnimCurves fieldCurves = new FieldAnimCurves();
  983. fieldCurves.type = type;
  984. fieldCurves.curveInfos = new CurveDrawInfo[4];
  985. string[] subPaths = { ".x", ".y", ".z", ".w" };
  986. for (int i = 0; i < subPaths.Length; i++)
  987. {
  988. string subFieldPath = path + subPaths[i];
  989. fieldCurves.curveInfos[i].curve = new EdAnimationCurve();
  990. selectedFields.Add(subFieldPath);
  991. }
  992. clipInfo.curves[path] = fieldCurves;
  993. }
  994. break;
  995. case SerializableProperty.FieldType.Vector3:
  996. {
  997. FieldAnimCurves fieldCurves = new FieldAnimCurves();
  998. fieldCurves.type = type;
  999. fieldCurves.curveInfos = new CurveDrawInfo[3];
  1000. string[] subPaths = { ".x", ".y", ".z" };
  1001. for (int i = 0; i < subPaths.Length; i++)
  1002. {
  1003. string subFieldPath = path + subPaths[i];
  1004. fieldCurves.curveInfos[i].curve = new EdAnimationCurve();
  1005. selectedFields.Add(subFieldPath);
  1006. }
  1007. clipInfo.curves[path] = fieldCurves;
  1008. }
  1009. break;
  1010. case SerializableProperty.FieldType.Vector2:
  1011. {
  1012. FieldAnimCurves fieldCurves = new FieldAnimCurves();
  1013. fieldCurves.type = type;
  1014. fieldCurves.curveInfos = new CurveDrawInfo[2];
  1015. string[] subPaths = { ".x", ".y" };
  1016. for (int i = 0; i < subPaths.Length; i++)
  1017. {
  1018. string subFieldPath = path + subPaths[i];
  1019. fieldCurves.curveInfos[i].curve = new EdAnimationCurve();
  1020. selectedFields.Add(subFieldPath);
  1021. }
  1022. clipInfo.curves[path] = fieldCurves;
  1023. }
  1024. break;
  1025. case SerializableProperty.FieldType.Color:
  1026. {
  1027. FieldAnimCurves fieldCurves = new FieldAnimCurves();
  1028. fieldCurves.type = type;
  1029. fieldCurves.curveInfos = new CurveDrawInfo[4];
  1030. string[] subPaths = { ".r", ".g", ".b", ".a" };
  1031. for (int i = 0; i < subPaths.Length; i++)
  1032. {
  1033. string subFieldPath = path + subPaths[i];
  1034. fieldCurves.curveInfos[i].curve = new EdAnimationCurve();
  1035. selectedFields.Add(subFieldPath);
  1036. }
  1037. clipInfo.curves[path] = fieldCurves;
  1038. }
  1039. break;
  1040. default: // Primitive type
  1041. {
  1042. FieldAnimCurves fieldCurves = new FieldAnimCurves();
  1043. fieldCurves.type = type;
  1044. fieldCurves.curveInfos = new CurveDrawInfo[1];
  1045. fieldCurves.curveInfos[0].curve = new EdAnimationCurve();
  1046. selectedFields.Add(path);
  1047. clipInfo.curves[path] = fieldCurves;
  1048. }
  1049. break;
  1050. }
  1051. UpdateCurveColors();
  1052. UpdateDisplayedFields();
  1053. EditorApplication.SetProjectDirty();
  1054. UpdateDisplayedCurves(noSelection);
  1055. }
  1056. /// <summary>
  1057. /// Selects a new animation curve field, making the curve display in the curve display GUI element.
  1058. /// </summary>
  1059. /// <param name="path">Path of the field to display.</param>
  1060. /// <param name="additive">If true the field will be shown along with any already selected fields, or if false
  1061. /// only the provided field will be shown.</param>
  1062. private void SelectField(string path, bool additive)
  1063. {
  1064. if (!additive)
  1065. selectedFields.Clear();
  1066. bool noSelection = selectedFields.Count == 0;
  1067. if (!string.IsNullOrEmpty(path))
  1068. {
  1069. selectedFields.RemoveAll(x => { return x == path || IsPathParent(x, path); });
  1070. selectedFields.Add(path);
  1071. }
  1072. guiFieldDisplay.SetSelection(selectedFields.ToArray());
  1073. UpdateDisplayedCurves(noSelection);
  1074. }
  1075. /// <summary>
  1076. /// Deletes all currently selecting fields, removing them their curves permanently.
  1077. /// </summary>
  1078. private void RemoveSelectedFields()
  1079. {
  1080. for (int i = 0; i < selectedFields.Count; i++)
  1081. clipInfo.curves.Remove(GetSubPathParent(selectedFields[i]));
  1082. UpdateCurveColors();
  1083. UpdateDisplayedFields();
  1084. selectedFields.Clear();
  1085. EditorApplication.SetProjectDirty();
  1086. UpdateDisplayedCurves();
  1087. }
  1088. /// <summary>
  1089. /// Updates the GUI element displaying the current animation curve fields.
  1090. /// </summary>
  1091. private void UpdateDisplayedFields()
  1092. {
  1093. List<AnimFieldInfo> existingFields = new List<AnimFieldInfo>();
  1094. foreach (var KVP in clipInfo.curves)
  1095. existingFields.Add(new AnimFieldInfo(KVP.Key, KVP.Value, !clipInfo.isImported));
  1096. guiFieldDisplay.SetFields(existingFields.ToArray());
  1097. }
  1098. #endregion
  1099. #region Helpers
  1100. /// <summary>
  1101. /// Returns the size of the curve editor GUI element.
  1102. /// </summary>
  1103. /// <returns>Width/height of the curve editor, in pixels.</returns>
  1104. private Vector2I GetCurveEditorSize()
  1105. {
  1106. Vector2I output = new Vector2I();
  1107. output.x = Math.Max(0, Width - FIELD_DISPLAY_WIDTH - scrollBarWidth);
  1108. output.y = Math.Max(0, Height - buttonLayoutHeight - scrollBarHeight);
  1109. return output;
  1110. }
  1111. /// <summary>
  1112. /// Calculates the total range covered by a set of curves.
  1113. /// </summary>
  1114. /// <param name="curveInfos">Curves to calculate range for.</param>
  1115. /// <param name="xRange">Maximum time value present in the curves.</param>
  1116. /// <param name="yRange">Maximum absolute curve value present in the curves.</param>
  1117. private static void CalculateRange(CurveDrawInfo[] curveInfos, out float xRange, out float yRange)
  1118. {
  1119. // Note: This only evaluates at keyframes, we should also evaluate in-between in order to account for steep
  1120. // tangents
  1121. xRange = 0.0f;
  1122. yRange = 0.0f;
  1123. foreach (var curveInfo in curveInfos)
  1124. {
  1125. KeyFrame[] keyframes = curveInfo.curve.KeyFrames;
  1126. foreach (var key in keyframes)
  1127. {
  1128. xRange = Math.Max(xRange, key.time);
  1129. yRange = Math.Max(yRange, Math.Abs(key.value));
  1130. }
  1131. }
  1132. }
  1133. /// <summary>
  1134. /// Attempts to find a curve field at the specified path.
  1135. /// </summary>
  1136. /// <param name="path">Path of the curve field to look for.</param>
  1137. /// <param name="curveInfos">One or multiple curves found for the specific path (one field can have multiple curves
  1138. /// if it is a complex type, like a vector).</param>
  1139. /// <returns>True if the curve field was found, false otherwise.</returns>
  1140. private bool TryGetCurve(string path, out CurveDrawInfo[] curveInfos)
  1141. {
  1142. int index = path.LastIndexOf(".");
  1143. string parentPath;
  1144. string subPathSuffix = null;
  1145. if (index == -1)
  1146. {
  1147. parentPath = path;
  1148. }
  1149. else
  1150. {
  1151. parentPath = path.Substring(0, index);
  1152. subPathSuffix = path.Substring(index, path.Length - index);
  1153. }
  1154. FieldAnimCurves fieldCurves;
  1155. if (clipInfo.curves.TryGetValue(parentPath, out fieldCurves))
  1156. {
  1157. if (!string.IsNullOrEmpty(subPathSuffix))
  1158. {
  1159. if (subPathSuffix == ".x" || subPathSuffix == ".r")
  1160. {
  1161. curveInfos = new [] { fieldCurves.curveInfos[0] };
  1162. return true;
  1163. }
  1164. else if (subPathSuffix == ".y" || subPathSuffix == ".g")
  1165. {
  1166. curveInfos = new[] { fieldCurves.curveInfos[1] };
  1167. return true;
  1168. }
  1169. else if (subPathSuffix == ".z" || subPathSuffix == ".b")
  1170. {
  1171. curveInfos = new[] { fieldCurves.curveInfos[2] };
  1172. return true;
  1173. }
  1174. else if (subPathSuffix == ".w" || subPathSuffix == ".a")
  1175. {
  1176. curveInfos = new[] { fieldCurves.curveInfos[3] };
  1177. return true;
  1178. }
  1179. }
  1180. else
  1181. {
  1182. curveInfos = fieldCurves.curveInfos;
  1183. return true;
  1184. }
  1185. }
  1186. curveInfos = new CurveDrawInfo[0];
  1187. return false;
  1188. }
  1189. /// <summary>
  1190. /// Checks if one curve field path a parent of the other.
  1191. /// </summary>
  1192. /// <param name="child">Path to check if it is a child of <paramref name="parent"/>.</param>
  1193. /// <param name="parent">Path to check if it is a parent of <paramref name="child"/>.</param>
  1194. /// <returns>True if <paramref name="child"/> is a child of <paramref name="parent"/>.</returns>
  1195. private bool IsPathParent(string child, string parent)
  1196. {
  1197. string[] childEntries = child.Split('/', '.');
  1198. string[] parentEntries = parent.Split('/', '.');
  1199. if (parentEntries.Length >= child.Length)
  1200. return false;
  1201. int compareLength = Math.Min(childEntries.Length, parentEntries.Length);
  1202. for (int i = 0; i < compareLength; i++)
  1203. {
  1204. if (childEntries[i] != parentEntries[i])
  1205. return false;
  1206. }
  1207. return true;
  1208. }
  1209. /// <summary>
  1210. /// If a path has sub-elements (e.g. .x, .r), returns a path without those elements. Otherwise returns the original
  1211. /// path.
  1212. /// </summary>
  1213. /// <param name="path">Path to check.</param>
  1214. /// <returns>Path without sub-elements.</returns>
  1215. private string GetSubPathParent(string path)
  1216. {
  1217. int index = path.LastIndexOf(".");
  1218. if (index == -1)
  1219. return path;
  1220. return path.Substring(0, index);
  1221. }
  1222. #endregion
  1223. #region Input callbacks
  1224. /// <summary>
  1225. /// Triggered when the user presses a mouse button.
  1226. /// </summary>
  1227. /// <param name="ev">Information about the mouse press event.</param>
  1228. private void OnPointerPressed(PointerEvent ev)
  1229. {
  1230. guiCurveEditor.OnPointerPressed(ev);
  1231. if (ev.button == PointerButton.Middle)
  1232. {
  1233. Vector2I windowPos = ScreenToWindowPos(ev.ScreenPos);
  1234. Vector2 curvePos;
  1235. if (guiCurveEditor.WindowToCurveSpace(windowPos, out curvePos))
  1236. {
  1237. dragStartPos = windowPos;
  1238. isButtonHeld = true;
  1239. }
  1240. }
  1241. }
  1242. /// <summary>
  1243. /// Triggered when the user moves the mouse.
  1244. /// </summary>
  1245. /// <param name="ev">Information about the mouse move event.</param>
  1246. private void OnPointerMoved(PointerEvent ev)
  1247. {
  1248. guiCurveEditor.OnPointerMoved(ev);
  1249. if (isButtonHeld)
  1250. {
  1251. Vector2I windowPos = ScreenToWindowPos(ev.ScreenPos);
  1252. int distance = Vector2I.Distance(dragStartPos, windowPos);
  1253. if (distance >= DRAG_START_DISTANCE)
  1254. {
  1255. isDragInProgress = true;
  1256. Cursor.Hide();
  1257. Rect2I clipRect;
  1258. clipRect.x = ev.ScreenPos.x - 2;
  1259. clipRect.y = ev.ScreenPos.y - 2;
  1260. clipRect.width = 4;
  1261. clipRect.height = 4;
  1262. Cursor.ClipToRect(clipRect);
  1263. }
  1264. }
  1265. }
  1266. /// <summary>
  1267. /// Triggered when the user releases a mouse button.
  1268. /// </summary>
  1269. /// <param name="ev">Information about the mouse release event.</param>
  1270. private void OnPointerReleased(PointerEvent ev)
  1271. {
  1272. if (isDragInProgress)
  1273. {
  1274. Cursor.Show();
  1275. Cursor.ClipDisable();
  1276. }
  1277. isButtonHeld = false;
  1278. isDragInProgress = false;
  1279. guiCurveEditor.OnPointerReleased(ev);
  1280. }
  1281. /// <summary>
  1282. /// Triggered when the user releases a keyboard button.
  1283. /// </summary>
  1284. /// <param name="ev">Information about the keyboard release event.</param>
  1285. private void OnButtonUp(ButtonEvent ev)
  1286. {
  1287. guiCurveEditor.OnButtonUp(ev);
  1288. }
  1289. #endregion
  1290. #region General callbacks
  1291. /// <summary>
  1292. /// Triggered by the field selector, when user selects a new curve field.
  1293. /// </summary>
  1294. /// <param name="path">Path of the selected curve field.</param>
  1295. /// <param name="type">Type of the selected curve field (float, vector, etc.).</param>
  1296. private void OnFieldAdded(string path, SerializableProperty.FieldType type)
  1297. {
  1298. // Remove the root scene object from the path (we know which SO it is, no need to hardcode its name in the path)
  1299. string pathNoRoot = path.TrimStart('/');
  1300. int separatorIdx = pathNoRoot.IndexOf("/");
  1301. if (separatorIdx == -1 || (separatorIdx + 1) >= pathNoRoot.Length)
  1302. return;
  1303. pathNoRoot = pathNoRoot.Substring(separatorIdx + 1, pathNoRoot.Length - separatorIdx - 1);
  1304. AddNewField(pathNoRoot, type);
  1305. ApplyClipChanges();
  1306. }
  1307. /// <summary>
  1308. /// Triggered when the user moves or resizes the horizontal scrollbar.
  1309. /// </summary>
  1310. /// <param name="position">New position of the scrollbar, in range [0, 1].</param>
  1311. /// <param name="size">New size of the scrollbar, in range [0, 1].</param>
  1312. private void OnHorzScrollOrResize(float position, float size)
  1313. {
  1314. SetHorzScrollbarProperties(position, size);
  1315. }
  1316. /// <summary>
  1317. /// Triggered when the user moves or resizes the vertical scrollbar.
  1318. /// </summary>
  1319. /// <param name="position">New position of the scrollbar, in range [0, 1].</param>
  1320. /// <param name="size">New size of the scrollbar, in range [0, 1].</param>
  1321. private void OnVertScrollOrResize(float position, float size)
  1322. {
  1323. SetVertScrollbarProperties(position, size);
  1324. }
  1325. /// <summary>
  1326. /// Triggered when the user selects a new curve field.
  1327. /// </summary>
  1328. /// <param name="path">Path of the selected curve field.</param>
  1329. private void OnFieldSelected(string path)
  1330. {
  1331. bool additive = Input.IsButtonHeld(ButtonCode.LeftShift) || Input.IsButtonHeld(ButtonCode.RightShift);
  1332. SelectField(path, additive);
  1333. }
  1334. /// <summary>
  1335. /// Triggered when the user selects a new scene object or a resource.
  1336. /// </summary>
  1337. /// <param name="sceneObjects">Newly selected scene objects.</param>
  1338. /// <param name="resourcePaths">Newly selected resources.</param>
  1339. private void OnSelectionChanged(SceneObject[] sceneObjects, string[] resourcePaths)
  1340. {
  1341. UpdateSelectedSO(false);
  1342. }
  1343. /// <summary>
  1344. /// Triggered when the user selects a new frame in the curve display.
  1345. /// </summary>
  1346. /// <param name="frameIdx">Index of the selected frame.</param>
  1347. private void OnFrameSelected(int frameIdx)
  1348. {
  1349. SetCurrentFrame(frameIdx);
  1350. }
  1351. /// <summary>
  1352. /// Triggered when the user changed (add, removed or modified) animation events in the curve display.
  1353. /// </summary>
  1354. private void OnEventsChanged()
  1355. {
  1356. clipInfo.events = guiCurveEditor.Events;
  1357. EditorApplication.SetProjectDirty();
  1358. }
  1359. #endregion
  1360. }
  1361. /// <summary>
  1362. /// Drop down window that displays options used by the animation window.
  1363. /// </summary>
  1364. [DefaultSize(100, 50)]
  1365. internal class AnimationOptions : DropDownWindow
  1366. {
  1367. /// <summary>
  1368. /// Initializes the drop down window by creating the necessary GUI. Must be called after construction and before
  1369. /// use.
  1370. /// </summary>
  1371. /// <param name="parent">Animation window that this drop down window is a part of.</param>
  1372. internal void Initialize(AnimationWindow parent)
  1373. {
  1374. GUIIntField fpsField = new GUIIntField(new LocEdString("FPS"), 40);
  1375. fpsField.Value = parent.FPS;
  1376. fpsField.OnChanged += x => { parent.FPS = x; };
  1377. GUILayoutY vertLayout = GUI.AddLayoutY();
  1378. vertLayout.AddFlexibleSpace();
  1379. GUILayoutX contentLayout = vertLayout.AddLayoutX();
  1380. contentLayout.AddFlexibleSpace();
  1381. contentLayout.AddElement(fpsField);
  1382. contentLayout.AddFlexibleSpace();
  1383. vertLayout.AddFlexibleSpace();
  1384. }
  1385. }
  1386. /** @} */
  1387. }