AnimationWindow.cs 62 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656
  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. private SerializedSceneObject soState;
  606. /// <summary>
  607. /// Transitions the window into a different state. Caller must validate state transitions.
  608. /// </summary>
  609. /// <param name="state">New state to transition to.</param>
  610. private void SwitchState(State state)
  611. {
  612. switch (this.state)
  613. {
  614. case State.Normal:
  615. {
  616. switch (state)
  617. {
  618. case State.Playback:
  619. StartPlayback();
  620. break;
  621. case State.Recording:
  622. StartRecord();
  623. break;
  624. }
  625. }
  626. break;
  627. case State.Playback:
  628. {
  629. switch (state)
  630. {
  631. case State.Normal:
  632. EndPlayback();
  633. break;
  634. case State.Recording:
  635. EndPlayback();
  636. StartRecord();
  637. break;
  638. }
  639. }
  640. break;
  641. case State.Recording:
  642. {
  643. switch (state)
  644. {
  645. case State.Normal:
  646. EndRecord();
  647. break;
  648. case State.Playback:
  649. EndRecord();
  650. StartPlayback();
  651. break;
  652. }
  653. }
  654. break;
  655. }
  656. this.state = state;
  657. }
  658. /// <summary>
  659. /// Plays back the animation on the currently selected object.
  660. /// </summary>
  661. private void StartPlayback()
  662. {
  663. EditorAnimClipTangents unused;
  664. clipInfo.Apply(out unused);
  665. soState = new SerializedSceneObject(selectedSO, true);
  666. Animation animation = selectedSO.GetComponent<Animation>();
  667. if (animation != null)
  668. {
  669. float time = guiCurveEditor.GetTimeForFrame(currentFrameIdx);
  670. animation.EditorPlay(clipInfo.clip, time);
  671. }
  672. playButton.Value = true;
  673. }
  674. /// <summary>
  675. /// Ends playback started with <see cref="StartPlayback"/>
  676. /// </summary>
  677. private void EndPlayback()
  678. {
  679. Animation animation = selectedSO.GetComponent<Animation>();
  680. if (animation != null)
  681. animation.EditorStop();
  682. if(soState != null)
  683. soState.Restore();
  684. playButton.Value = false;
  685. }
  686. /// <summary>
  687. /// Start recording modifications made to the selected scene object.
  688. /// </summary>
  689. private void StartRecord()
  690. {
  691. // TODO
  692. recordButton.Value = true;
  693. }
  694. /// <summary>
  695. /// Stops recording modifications made to the selected scene object.
  696. /// </summary>
  697. private void EndRecord()
  698. {
  699. // TODO
  700. // TODO - Lock selection while active? (Don't allow another object to become anim focus in order to allow modifications on anim children).
  701. recordButton.Value = false;
  702. }
  703. /// <summary>
  704. /// Iterates over all curve path fields and records their current state. If the state differs from the current
  705. /// curve values, new keyframes are added.
  706. /// </summary>
  707. /// <param name="time">Time for which to record the state, in seconds.</param>
  708. private void RecordState(float time)
  709. {
  710. Action<EdAnimationCurve, float, float> addOrUpdateKeyframe = (curve, keyTime, keyValue) =>
  711. {
  712. KeyFrame[] keyframes = curve.KeyFrames;
  713. int keyframeIdx = -1;
  714. for (int i = 0; i < keyframes.Length; i++)
  715. {
  716. if (MathEx.ApproxEquals(keyframes[i].time, time))
  717. {
  718. keyframeIdx = i;
  719. break;
  720. }
  721. }
  722. if (keyframeIdx != -1)
  723. curve.UpdateKeyframe(keyframeIdx, keyTime, keyValue);
  724. else
  725. curve.AddKeyframe(keyTime, keyValue);
  726. curve.Apply();
  727. };
  728. foreach (var KVP in clipInfo.curves)
  729. {
  730. string suffix;
  731. SerializableProperty property = Animation.FindProperty(selectedSO, KVP.Key, out suffix);
  732. if (property == null)
  733. continue;
  734. switch (KVP.Value.type)
  735. {
  736. case SerializableProperty.FieldType.Vector2:
  737. {
  738. Vector2 value = property.GetValue<Vector2>();
  739. for (int i = 0; i < 2; i++)
  740. {
  741. float curveVal = KVP.Value.curveInfos[i].curve.Evaluate(time);
  742. if (!MathEx.ApproxEquals(value[i], curveVal))
  743. addOrUpdateKeyframe(KVP.Value.curveInfos[i].curve, time, curveVal);
  744. }
  745. }
  746. break;
  747. case SerializableProperty.FieldType.Vector3:
  748. {
  749. Vector3 value = property.GetValue<Vector3>();
  750. for (int i = 0; i < 3; i++)
  751. {
  752. float curveVal = KVP.Value.curveInfos[i].curve.Evaluate(time);
  753. if (!MathEx.ApproxEquals(value[i], curveVal))
  754. addOrUpdateKeyframe(KVP.Value.curveInfos[i].curve, time, curveVal);
  755. }
  756. }
  757. break;
  758. case SerializableProperty.FieldType.Vector4:
  759. {
  760. if (property.InternalType == typeof(Vector4))
  761. {
  762. Vector4 value = property.GetValue<Vector4>();
  763. for (int i = 0; i < 4; i++)
  764. {
  765. float curveVal = KVP.Value.curveInfos[i].curve.Evaluate(time);
  766. if (!MathEx.ApproxEquals(value[i], curveVal))
  767. addOrUpdateKeyframe(KVP.Value.curveInfos[i].curve, time, curveVal);
  768. }
  769. }
  770. else if (property.InternalType == typeof(Quaternion))
  771. {
  772. Quaternion value = property.GetValue<Quaternion>();
  773. for (int i = 0; i < 4; i++)
  774. {
  775. float curveVal = KVP.Value.curveInfos[i].curve.Evaluate(time);
  776. if (!MathEx.ApproxEquals(value[i], curveVal))
  777. addOrUpdateKeyframe(KVP.Value.curveInfos[i].curve, time, curveVal);
  778. }
  779. }
  780. }
  781. break;
  782. case SerializableProperty.FieldType.Color:
  783. {
  784. Color value = property.GetValue<Color>();
  785. for (int i = 0; i < 4; i++)
  786. {
  787. float curveVal = KVP.Value.curveInfos[i].curve.Evaluate(time);
  788. if (!MathEx.ApproxEquals(value[i], curveVal))
  789. addOrUpdateKeyframe(KVP.Value.curveInfos[i].curve, time, curveVal);
  790. }
  791. }
  792. break;
  793. case SerializableProperty.FieldType.Bool:
  794. {
  795. bool value = property.GetValue<bool>();
  796. bool curveVal = KVP.Value.curveInfos[0].curve.Evaluate(time) > 0.0f;
  797. if (value != curveVal)
  798. addOrUpdateKeyframe(KVP.Value.curveInfos[0].curve, time, curveVal ? 1.0f : -1.0f);
  799. }
  800. break;
  801. case SerializableProperty.FieldType.Int:
  802. {
  803. int value = property.GetValue<int>();
  804. int curveVal = (int)KVP.Value.curveInfos[0].curve.Evaluate(time);
  805. if (value != curveVal)
  806. addOrUpdateKeyframe(KVP.Value.curveInfos[0].curve, time, curveVal);
  807. }
  808. break;
  809. case SerializableProperty.FieldType.Float:
  810. {
  811. float value = property.GetValue<float>();
  812. float curveVal = KVP.Value.curveInfos[0].curve.Evaluate(time);
  813. if (!MathEx.ApproxEquals(value, curveVal))
  814. addOrUpdateKeyframe(KVP.Value.curveInfos[0].curve, time, curveVal);
  815. }
  816. break;
  817. }
  818. }
  819. }
  820. #endregion
  821. #region Curve display
  822. private int currentFrameIdx;
  823. private int fps = 1;
  824. /// <summary>
  825. /// Sampling rate of the animation in frames per second. Determines granularity at which positions keyframes can be
  826. /// placed.
  827. /// </summary>
  828. internal int FPS
  829. {
  830. get { return fps; }
  831. set { guiCurveEditor.SetFPS(value); fps = MathEx.Max(value, 1); }
  832. }
  833. /// <summary>
  834. /// Changes the currently selected frame in the curve display.
  835. /// </summary>
  836. /// <param name="frameIdx">Index of the frame to select.</param>
  837. private void SetCurrentFrame(int frameIdx)
  838. {
  839. currentFrameIdx = Math.Max(0, frameIdx);
  840. frameInputField.Value = currentFrameIdx;
  841. guiCurveEditor.SetMarkedFrame(currentFrameIdx);
  842. float time = guiCurveEditor.GetTimeForFrame(currentFrameIdx);
  843. List<GUIAnimFieldPathValue> values = new List<GUIAnimFieldPathValue>();
  844. foreach (var kvp in clipInfo.curves)
  845. {
  846. GUIAnimFieldPathValue fieldValue = new GUIAnimFieldPathValue();
  847. fieldValue.path = kvp.Key;
  848. switch (kvp.Value.type)
  849. {
  850. case SerializableProperty.FieldType.Vector2:
  851. {
  852. Vector2 value = new Vector2();
  853. for (int i = 0; i < 2; i++)
  854. value[i] = kvp.Value.curveInfos[i].curve.Evaluate(time, false);
  855. fieldValue.value = value;
  856. }
  857. break;
  858. case SerializableProperty.FieldType.Vector3:
  859. {
  860. Vector3 value = new Vector3();
  861. for (int i = 0; i < 3; i++)
  862. value[i] = kvp.Value.curveInfos[i].curve.Evaluate(time, false);
  863. fieldValue.value = value;
  864. }
  865. break;
  866. case SerializableProperty.FieldType.Vector4:
  867. {
  868. Vector4 value = new Vector4();
  869. for (int i = 0; i < 4; i++)
  870. value[i] = kvp.Value.curveInfos[i].curve.Evaluate(time, false);
  871. fieldValue.value = value;
  872. }
  873. break;
  874. case SerializableProperty.FieldType.Color:
  875. {
  876. Color value = new Color();
  877. for (int i = 0; i < 4; i++)
  878. value[i] = kvp.Value.curveInfos[i].curve.Evaluate(time, false);
  879. fieldValue.value = value;
  880. }
  881. break;
  882. case SerializableProperty.FieldType.Bool:
  883. case SerializableProperty.FieldType.Int:
  884. case SerializableProperty.FieldType.Float:
  885. fieldValue.value = kvp.Value.curveInfos[0].curve.Evaluate(time, false); ;
  886. break;
  887. }
  888. values.Add(fieldValue);
  889. }
  890. guiFieldDisplay.SetDisplayValues(values.ToArray());
  891. }
  892. /// <summary>
  893. /// Returns a list of all animation curves that should be displayed in the curve display.
  894. /// </summary>
  895. /// <returns>Array of curves to display.</returns>
  896. private CurveDrawInfo[] GetDisplayedCurves()
  897. {
  898. List<CurveDrawInfo> curvesToDisplay = new List<CurveDrawInfo>();
  899. if (selectedFields.Count == 0) // Display all if nothing is selected
  900. {
  901. if (clipInfo == null)
  902. return curvesToDisplay.ToArray();
  903. foreach (var curve in clipInfo.curves)
  904. {
  905. for (int i = 0; i < curve.Value.curveInfos.Length; i++)
  906. curvesToDisplay.Add(curve.Value.curveInfos[i]);
  907. }
  908. }
  909. else
  910. {
  911. for (int i = 0; i < selectedFields.Count; i++)
  912. {
  913. CurveDrawInfo[] curveInfos;
  914. if (TryGetCurve(selectedFields[i], out curveInfos))
  915. curvesToDisplay.AddRange(curveInfos);
  916. }
  917. }
  918. return curvesToDisplay.ToArray();
  919. }
  920. /// <summary>
  921. /// Returns width/height required to show the entire contents of the currently displayed curves.
  922. /// </summary>
  923. /// <returns>Width/height of the curve area, in curve space (value, time).</returns>
  924. private Vector2 GetOptimalRange()
  925. {
  926. CurveDrawInfo[] curvesToDisplay = GetDisplayedCurves();
  927. float xRange;
  928. float yRange;
  929. CalculateRange(curvesToDisplay, out xRange, out yRange);
  930. // Add padding to y range
  931. yRange *= 1.05f;
  932. // Don't allow zero range
  933. if (xRange == 0.0f)
  934. xRange = 60.0f;
  935. if (yRange == 0.0f)
  936. yRange = 10.0f;
  937. return new Vector2(xRange, yRange);
  938. }
  939. /// <summary>
  940. /// Calculates an unique color for each animation curve.
  941. /// </summary>
  942. private void UpdateCurveColors()
  943. {
  944. int globalCurveIdx = 0;
  945. foreach (var curveGroup in clipInfo.curves)
  946. {
  947. for (int i = 0; i < curveGroup.Value.curveInfos.Length; i++)
  948. curveGroup.Value.curveInfos[i].color = GUICurveDrawing.GetUniqueColor(globalCurveIdx++);
  949. }
  950. }
  951. /// <summary>
  952. /// Updates the curve display with currently selected curves.
  953. /// </summary>
  954. /// <param name="allowReduce">Normally the curve display will expand if newly selected curves cover a larger area
  955. /// than currently available, but the area won't be reduced if the selected curves cover
  956. /// a smaller area. Set this to true to allow the area to be reduced.</param>
  957. private void UpdateDisplayedCurves(bool allowReduce = false)
  958. {
  959. CurveDrawInfo[] curvesToDisplay = GetDisplayedCurves();
  960. guiCurveEditor.SetCurves(curvesToDisplay);
  961. Vector2 newRange = GetOptimalRange();
  962. if (!allowReduce)
  963. {
  964. // Don't reduce visible range
  965. newRange.x = Math.Max(newRange.x, guiCurveEditor.Range.x);
  966. newRange.y = Math.Max(newRange.y, guiCurveEditor.Range.y);
  967. }
  968. guiCurveEditor.Range = newRange;
  969. UpdateScrollBarSize();
  970. }
  971. #endregion
  972. #region Field display
  973. private List<string> selectedFields = new List<string>();
  974. /// <summary>
  975. /// Registers a new animation curve field.
  976. /// </summary>
  977. /// <param name="path">Path of the field, see <see cref="GUIFieldSelector.OnElementSelected"/></param>
  978. /// <param name="type">Type of the field (float, vector, etc.)</param>
  979. private void AddNewField(string path, SerializableProperty.FieldType type)
  980. {
  981. bool noSelection = selectedFields.Count == 0;
  982. switch (type)
  983. {
  984. case SerializableProperty.FieldType.Vector4:
  985. {
  986. FieldAnimCurves fieldCurves = new FieldAnimCurves();
  987. fieldCurves.type = type;
  988. fieldCurves.curveInfos = new CurveDrawInfo[4];
  989. string[] subPaths = { ".x", ".y", ".z", ".w" };
  990. for (int i = 0; i < subPaths.Length; i++)
  991. {
  992. string subFieldPath = path + subPaths[i];
  993. fieldCurves.curveInfos[i].curve = new EdAnimationCurve();
  994. selectedFields.Add(subFieldPath);
  995. }
  996. clipInfo.curves[path] = fieldCurves;
  997. }
  998. break;
  999. case SerializableProperty.FieldType.Vector3:
  1000. {
  1001. FieldAnimCurves fieldCurves = new FieldAnimCurves();
  1002. fieldCurves.type = type;
  1003. fieldCurves.curveInfos = new CurveDrawInfo[3];
  1004. string[] subPaths = { ".x", ".y", ".z" };
  1005. for (int i = 0; i < subPaths.Length; i++)
  1006. {
  1007. string subFieldPath = path + subPaths[i];
  1008. fieldCurves.curveInfos[i].curve = new EdAnimationCurve();
  1009. selectedFields.Add(subFieldPath);
  1010. }
  1011. clipInfo.curves[path] = fieldCurves;
  1012. }
  1013. break;
  1014. case SerializableProperty.FieldType.Vector2:
  1015. {
  1016. FieldAnimCurves fieldCurves = new FieldAnimCurves();
  1017. fieldCurves.type = type;
  1018. fieldCurves.curveInfos = new CurveDrawInfo[2];
  1019. string[] subPaths = { ".x", ".y" };
  1020. for (int i = 0; i < subPaths.Length; i++)
  1021. {
  1022. string subFieldPath = path + subPaths[i];
  1023. fieldCurves.curveInfos[i].curve = new EdAnimationCurve();
  1024. selectedFields.Add(subFieldPath);
  1025. }
  1026. clipInfo.curves[path] = fieldCurves;
  1027. }
  1028. break;
  1029. case SerializableProperty.FieldType.Color:
  1030. {
  1031. FieldAnimCurves fieldCurves = new FieldAnimCurves();
  1032. fieldCurves.type = type;
  1033. fieldCurves.curveInfos = new CurveDrawInfo[4];
  1034. string[] subPaths = { ".r", ".g", ".b", ".a" };
  1035. for (int i = 0; i < subPaths.Length; i++)
  1036. {
  1037. string subFieldPath = path + subPaths[i];
  1038. fieldCurves.curveInfos[i].curve = new EdAnimationCurve();
  1039. selectedFields.Add(subFieldPath);
  1040. }
  1041. clipInfo.curves[path] = fieldCurves;
  1042. }
  1043. break;
  1044. default: // Primitive type
  1045. {
  1046. FieldAnimCurves fieldCurves = new FieldAnimCurves();
  1047. fieldCurves.type = type;
  1048. fieldCurves.curveInfos = new CurveDrawInfo[1];
  1049. fieldCurves.curveInfos[0].curve = new EdAnimationCurve();
  1050. selectedFields.Add(path);
  1051. clipInfo.curves[path] = fieldCurves;
  1052. }
  1053. break;
  1054. }
  1055. UpdateCurveColors();
  1056. UpdateDisplayedFields();
  1057. EditorApplication.SetProjectDirty();
  1058. UpdateDisplayedCurves(noSelection);
  1059. }
  1060. /// <summary>
  1061. /// Selects a new animation curve field, making the curve display in the curve display GUI element.
  1062. /// </summary>
  1063. /// <param name="path">Path of the field to display.</param>
  1064. /// <param name="additive">If true the field will be shown along with any already selected fields, or if false
  1065. /// only the provided field will be shown.</param>
  1066. private void SelectField(string path, bool additive)
  1067. {
  1068. if (!additive)
  1069. selectedFields.Clear();
  1070. bool noSelection = selectedFields.Count == 0;
  1071. if (!string.IsNullOrEmpty(path))
  1072. {
  1073. selectedFields.RemoveAll(x => { return x == path || IsPathParent(x, path); });
  1074. selectedFields.Add(path);
  1075. }
  1076. guiFieldDisplay.SetSelection(selectedFields.ToArray());
  1077. UpdateDisplayedCurves(noSelection);
  1078. }
  1079. /// <summary>
  1080. /// Deletes all currently selecting fields, removing them their curves permanently.
  1081. /// </summary>
  1082. private void RemoveSelectedFields()
  1083. {
  1084. for (int i = 0; i < selectedFields.Count; i++)
  1085. clipInfo.curves.Remove(GetSubPathParent(selectedFields[i]));
  1086. UpdateCurveColors();
  1087. UpdateDisplayedFields();
  1088. selectedFields.Clear();
  1089. EditorApplication.SetProjectDirty();
  1090. UpdateDisplayedCurves();
  1091. }
  1092. /// <summary>
  1093. /// Updates the GUI element displaying the current animation curve fields.
  1094. /// </summary>
  1095. private void UpdateDisplayedFields()
  1096. {
  1097. List<AnimFieldInfo> existingFields = new List<AnimFieldInfo>();
  1098. foreach (var KVP in clipInfo.curves)
  1099. existingFields.Add(new AnimFieldInfo(KVP.Key, KVP.Value, !clipInfo.isImported));
  1100. guiFieldDisplay.SetFields(existingFields.ToArray());
  1101. }
  1102. #endregion
  1103. #region Helpers
  1104. /// <summary>
  1105. /// Returns the size of the curve editor GUI element.
  1106. /// </summary>
  1107. /// <returns>Width/height of the curve editor, in pixels.</returns>
  1108. private Vector2I GetCurveEditorSize()
  1109. {
  1110. Vector2I output = new Vector2I();
  1111. output.x = Math.Max(0, Width - FIELD_DISPLAY_WIDTH - scrollBarWidth);
  1112. output.y = Math.Max(0, Height - buttonLayoutHeight - scrollBarHeight);
  1113. return output;
  1114. }
  1115. /// <summary>
  1116. /// Calculates the total range covered by a set of curves.
  1117. /// </summary>
  1118. /// <param name="curveInfos">Curves to calculate range for.</param>
  1119. /// <param name="xRange">Maximum time value present in the curves.</param>
  1120. /// <param name="yRange">Maximum absolute curve value present in the curves.</param>
  1121. private static void CalculateRange(CurveDrawInfo[] curveInfos, out float xRange, out float yRange)
  1122. {
  1123. // Note: This only evaluates at keyframes, we should also evaluate in-between in order to account for steep
  1124. // tangents
  1125. xRange = 0.0f;
  1126. yRange = 0.0f;
  1127. foreach (var curveInfo in curveInfos)
  1128. {
  1129. KeyFrame[] keyframes = curveInfo.curve.KeyFrames;
  1130. foreach (var key in keyframes)
  1131. {
  1132. xRange = Math.Max(xRange, key.time);
  1133. yRange = Math.Max(yRange, Math.Abs(key.value));
  1134. }
  1135. }
  1136. }
  1137. /// <summary>
  1138. /// Attempts to find a curve field at the specified path.
  1139. /// </summary>
  1140. /// <param name="path">Path of the curve field to look for.</param>
  1141. /// <param name="curveInfos">One or multiple curves found for the specific path (one field can have multiple curves
  1142. /// if it is a complex type, like a vector).</param>
  1143. /// <returns>True if the curve field was found, false otherwise.</returns>
  1144. private bool TryGetCurve(string path, out CurveDrawInfo[] curveInfos)
  1145. {
  1146. int index = path.LastIndexOf(".");
  1147. string parentPath;
  1148. string subPathSuffix = null;
  1149. if (index == -1)
  1150. {
  1151. parentPath = path;
  1152. }
  1153. else
  1154. {
  1155. parentPath = path.Substring(0, index);
  1156. subPathSuffix = path.Substring(index, path.Length - index);
  1157. }
  1158. FieldAnimCurves fieldCurves;
  1159. if (clipInfo.curves.TryGetValue(parentPath, out fieldCurves))
  1160. {
  1161. if (!string.IsNullOrEmpty(subPathSuffix))
  1162. {
  1163. if (subPathSuffix == ".x" || subPathSuffix == ".r")
  1164. {
  1165. curveInfos = new [] { fieldCurves.curveInfos[0] };
  1166. return true;
  1167. }
  1168. else if (subPathSuffix == ".y" || subPathSuffix == ".g")
  1169. {
  1170. curveInfos = new[] { fieldCurves.curveInfos[1] };
  1171. return true;
  1172. }
  1173. else if (subPathSuffix == ".z" || subPathSuffix == ".b")
  1174. {
  1175. curveInfos = new[] { fieldCurves.curveInfos[2] };
  1176. return true;
  1177. }
  1178. else if (subPathSuffix == ".w" || subPathSuffix == ".a")
  1179. {
  1180. curveInfos = new[] { fieldCurves.curveInfos[3] };
  1181. return true;
  1182. }
  1183. }
  1184. else
  1185. {
  1186. curveInfos = fieldCurves.curveInfos;
  1187. return true;
  1188. }
  1189. }
  1190. curveInfos = new CurveDrawInfo[0];
  1191. return false;
  1192. }
  1193. /// <summary>
  1194. /// Checks if one curve field path a parent of the other.
  1195. /// </summary>
  1196. /// <param name="child">Path to check if it is a child of <paramref name="parent"/>.</param>
  1197. /// <param name="parent">Path to check if it is a parent of <paramref name="child"/>.</param>
  1198. /// <returns>True if <paramref name="child"/> is a child of <paramref name="parent"/>.</returns>
  1199. private bool IsPathParent(string child, string parent)
  1200. {
  1201. string[] childEntries = child.Split('/', '.');
  1202. string[] parentEntries = parent.Split('/', '.');
  1203. if (parentEntries.Length >= child.Length)
  1204. return false;
  1205. int compareLength = Math.Min(childEntries.Length, parentEntries.Length);
  1206. for (int i = 0; i < compareLength; i++)
  1207. {
  1208. if (childEntries[i] != parentEntries[i])
  1209. return false;
  1210. }
  1211. return true;
  1212. }
  1213. /// <summary>
  1214. /// If a path has sub-elements (e.g. .x, .r), returns a path without those elements. Otherwise returns the original
  1215. /// path.
  1216. /// </summary>
  1217. /// <param name="path">Path to check.</param>
  1218. /// <returns>Path without sub-elements.</returns>
  1219. private string GetSubPathParent(string path)
  1220. {
  1221. int index = path.LastIndexOf(".");
  1222. if (index == -1)
  1223. return path;
  1224. return path.Substring(0, index);
  1225. }
  1226. #endregion
  1227. #region Input callbacks
  1228. /// <summary>
  1229. /// Triggered when the user presses a mouse button.
  1230. /// </summary>
  1231. /// <param name="ev">Information about the mouse press event.</param>
  1232. private void OnPointerPressed(PointerEvent ev)
  1233. {
  1234. guiCurveEditor.OnPointerPressed(ev);
  1235. if (ev.button == PointerButton.Middle)
  1236. {
  1237. Vector2I windowPos = ScreenToWindowPos(ev.ScreenPos);
  1238. Vector2 curvePos;
  1239. if (guiCurveEditor.WindowToCurveSpace(windowPos, out curvePos))
  1240. {
  1241. dragStartPos = windowPos;
  1242. isButtonHeld = true;
  1243. }
  1244. }
  1245. }
  1246. /// <summary>
  1247. /// Triggered when the user moves the mouse.
  1248. /// </summary>
  1249. /// <param name="ev">Information about the mouse move event.</param>
  1250. private void OnPointerMoved(PointerEvent ev)
  1251. {
  1252. guiCurveEditor.OnPointerMoved(ev);
  1253. if (isButtonHeld)
  1254. {
  1255. Vector2I windowPos = ScreenToWindowPos(ev.ScreenPos);
  1256. int distance = Vector2I.Distance(dragStartPos, windowPos);
  1257. if (distance >= DRAG_START_DISTANCE)
  1258. {
  1259. isDragInProgress = true;
  1260. Cursor.Hide();
  1261. Rect2I clipRect;
  1262. clipRect.x = ev.ScreenPos.x - 2;
  1263. clipRect.y = ev.ScreenPos.y - 2;
  1264. clipRect.width = 4;
  1265. clipRect.height = 4;
  1266. Cursor.ClipToRect(clipRect);
  1267. }
  1268. }
  1269. }
  1270. /// <summary>
  1271. /// Triggered when the user releases a mouse button.
  1272. /// </summary>
  1273. /// <param name="ev">Information about the mouse release event.</param>
  1274. private void OnPointerReleased(PointerEvent ev)
  1275. {
  1276. if (isDragInProgress)
  1277. {
  1278. Cursor.Show();
  1279. Cursor.ClipDisable();
  1280. }
  1281. isButtonHeld = false;
  1282. isDragInProgress = false;
  1283. guiCurveEditor.OnPointerReleased(ev);
  1284. }
  1285. /// <summary>
  1286. /// Triggered when the user releases a keyboard button.
  1287. /// </summary>
  1288. /// <param name="ev">Information about the keyboard release event.</param>
  1289. private void OnButtonUp(ButtonEvent ev)
  1290. {
  1291. guiCurveEditor.OnButtonUp(ev);
  1292. }
  1293. #endregion
  1294. #region General callbacks
  1295. /// <summary>
  1296. /// Triggered by the field selector, when user selects a new curve field.
  1297. /// </summary>
  1298. /// <param name="path">Path of the selected curve field.</param>
  1299. /// <param name="type">Type of the selected curve field (float, vector, etc.).</param>
  1300. private void OnFieldAdded(string path, SerializableProperty.FieldType type)
  1301. {
  1302. // Remove the root scene object from the path (we know which SO it is, no need to hardcode its name in the path)
  1303. string pathNoRoot = path.TrimStart('/');
  1304. int separatorIdx = pathNoRoot.IndexOf("/");
  1305. if (separatorIdx == -1 || (separatorIdx + 1) >= pathNoRoot.Length)
  1306. return;
  1307. pathNoRoot = pathNoRoot.Substring(separatorIdx + 1, pathNoRoot.Length - separatorIdx - 1);
  1308. AddNewField(pathNoRoot, type);
  1309. ApplyClipChanges();
  1310. }
  1311. /// <summary>
  1312. /// Triggered when the user moves or resizes the horizontal scrollbar.
  1313. /// </summary>
  1314. /// <param name="position">New position of the scrollbar, in range [0, 1].</param>
  1315. /// <param name="size">New size of the scrollbar, in range [0, 1].</param>
  1316. private void OnHorzScrollOrResize(float position, float size)
  1317. {
  1318. SetHorzScrollbarProperties(position, size);
  1319. }
  1320. /// <summary>
  1321. /// Triggered when the user moves or resizes the vertical scrollbar.
  1322. /// </summary>
  1323. /// <param name="position">New position of the scrollbar, in range [0, 1].</param>
  1324. /// <param name="size">New size of the scrollbar, in range [0, 1].</param>
  1325. private void OnVertScrollOrResize(float position, float size)
  1326. {
  1327. SetVertScrollbarProperties(position, size);
  1328. }
  1329. /// <summary>
  1330. /// Triggered when the user selects a new curve field.
  1331. /// </summary>
  1332. /// <param name="path">Path of the selected curve field.</param>
  1333. private void OnFieldSelected(string path)
  1334. {
  1335. bool additive = Input.IsButtonHeld(ButtonCode.LeftShift) || Input.IsButtonHeld(ButtonCode.RightShift);
  1336. SelectField(path, additive);
  1337. }
  1338. /// <summary>
  1339. /// Triggered when the user selects a new scene object or a resource.
  1340. /// </summary>
  1341. /// <param name="sceneObjects">Newly selected scene objects.</param>
  1342. /// <param name="resourcePaths">Newly selected resources.</param>
  1343. private void OnSelectionChanged(SceneObject[] sceneObjects, string[] resourcePaths)
  1344. {
  1345. UpdateSelectedSO(false);
  1346. }
  1347. /// <summary>
  1348. /// Triggered when the user selects a new frame in the curve display.
  1349. /// </summary>
  1350. /// <param name="frameIdx">Index of the selected frame.</param>
  1351. private void OnFrameSelected(int frameIdx)
  1352. {
  1353. SetCurrentFrame(frameIdx);
  1354. }
  1355. /// <summary>
  1356. /// Triggered when the user changed (add, removed or modified) animation events in the curve display.
  1357. /// </summary>
  1358. private void OnEventsChanged()
  1359. {
  1360. clipInfo.events = guiCurveEditor.Events;
  1361. EditorApplication.SetProjectDirty();
  1362. }
  1363. #endregion
  1364. }
  1365. /// <summary>
  1366. /// Drop down window that displays options used by the animation window.
  1367. /// </summary>
  1368. [DefaultSize(100, 50)]
  1369. internal class AnimationOptions : DropDownWindow
  1370. {
  1371. /// <summary>
  1372. /// Initializes the drop down window by creating the necessary GUI. Must be called after construction and before
  1373. /// use.
  1374. /// </summary>
  1375. /// <param name="parent">Animation window that this drop down window is a part of.</param>
  1376. internal void Initialize(AnimationWindow parent)
  1377. {
  1378. GUIIntField fpsField = new GUIIntField(new LocEdString("FPS"), 40);
  1379. fpsField.Value = parent.FPS;
  1380. fpsField.OnChanged += x => { parent.FPS = x; };
  1381. GUILayoutY vertLayout = GUI.AddLayoutY();
  1382. vertLayout.AddFlexibleSpace();
  1383. GUILayoutX contentLayout = vertLayout.AddLayoutX();
  1384. contentLayout.AddFlexibleSpace();
  1385. contentLayout.AddElement(fpsField);
  1386. contentLayout.AddFlexibleSpace();
  1387. vertLayout.AddFlexibleSpace();
  1388. }
  1389. }
  1390. /** @} */
  1391. }