AnimationWindow.cs 42 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203
  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.Text;
  6. using BansheeEngine;
  7. namespace BansheeEditor
  8. {
  9. /** @addtogroup Windows
  10. * @{
  11. */
  12. /// <summary>
  13. /// Displays animation curve editor window.
  14. /// </summary>
  15. [DefaultSize(900, 500)]
  16. internal class AnimationWindow : EditorWindow
  17. {
  18. private const int FIELD_DISPLAY_WIDTH = 200;
  19. private const int DRAG_START_DISTANCE = 3;
  20. private const float DRAG_SCALE = 10.0f;
  21. private const float ZOOM_SCALE = 0.1f/120.0f; // One scroll step is usually 120 units, we want 1/10 of that
  22. private bool isInitialized;
  23. private SceneObject selectedSO;
  24. #region Overrides
  25. /// <summary>
  26. /// Opens the animation window.
  27. /// </summary>
  28. [MenuItem("Windows/Animation", ButtonModifier.CtrlAlt, ButtonCode.A, 6000)]
  29. private static void OpenGameWindow()
  30. {
  31. OpenWindow<AnimationWindow>();
  32. }
  33. /// <inheritdoc/>
  34. protected override LocString GetDisplayName()
  35. {
  36. return new LocEdString("Animation");
  37. }
  38. private void OnInitialize()
  39. {
  40. Selection.OnSelectionChanged += OnSelectionChanged;
  41. EditorInput.OnPointerPressed += OnPointerPressed;
  42. EditorInput.OnPointerMoved += OnPointerMoved;
  43. EditorInput.OnPointerReleased += OnPointerReleased;
  44. EditorInput.OnButtonUp += OnButtonUp;
  45. RebuildGUI();
  46. }
  47. private void OnEditorUpdate()
  48. {
  49. if (!isInitialized)
  50. return;
  51. HandleDragAndZoomInput();
  52. }
  53. private void OnDestroy()
  54. {
  55. Selection.OnSelectionChanged -= OnSelectionChanged;
  56. EditorInput.OnPointerPressed -= OnPointerPressed;
  57. EditorInput.OnPointerMoved -= OnPointerMoved;
  58. EditorInput.OnPointerReleased -= OnPointerReleased;
  59. EditorInput.OnButtonUp -= OnButtonUp;
  60. }
  61. protected override void WindowResized(int width, int height)
  62. {
  63. if (!isInitialized)
  64. return;
  65. ResizeGUI(width, height);
  66. }
  67. #endregion
  68. #region GUI
  69. private GUIButton playButton;
  70. private GUIButton recordButton;
  71. private GUIButton prevFrameButton;
  72. private GUIIntField frameInputField;
  73. private GUIButton nextFrameButton;
  74. private GUIButton addKeyframeButton;
  75. private GUIButton addEventButton;
  76. private GUIButton optionsButton;
  77. private GUIButton addPropertyBtn;
  78. private GUIButton delPropertyBtn;
  79. private GUILayout buttonLayout;
  80. private int buttonLayoutHeight;
  81. private int scrollBarWidth;
  82. private int scrollBarHeight;
  83. private GUIResizeableScrollBarH horzScrollBar;
  84. private GUIResizeableScrollBarV vertScrollBar;
  85. private GUIPanel editorPanel;
  86. private GUIAnimFieldDisplay guiFieldDisplay;
  87. private GUICurveEditor guiCurveEditor;
  88. private void RebuildGUI()
  89. {
  90. GUI.Clear();
  91. selectedFields.Clear();
  92. curves.Clear();
  93. isInitialized = false;
  94. if (selectedSO != Selection.SceneObject)
  95. {
  96. zoomAmount = 0.0f;
  97. selectedSO = Selection.SceneObject;
  98. }
  99. if (selectedSO == null)
  100. {
  101. GUILabel warningLbl = new GUILabel(new LocEdString("Select an object to animate in the Hierarchy or Scene windows."));
  102. GUILayoutY vertLayout = GUI.AddLayoutY();
  103. vertLayout.AddFlexibleSpace();
  104. GUILayoutX horzLayout = vertLayout.AddLayoutX();
  105. vertLayout.AddFlexibleSpace();
  106. horzLayout.AddFlexibleSpace();
  107. horzLayout.AddElement(warningLbl);
  108. horzLayout.AddFlexibleSpace();
  109. return;
  110. }
  111. // TODO - Retrieve Animation & AnimationClip from the selected object, fill curves dictionary
  112. // - If not available, show a button to create new animation clip
  113. // Top button row
  114. GUIContent playIcon = new GUIContent(EditorBuiltin.GetAnimationWindowIcon(AnimationWindowIcon.Play),
  115. new LocEdString("Play"));
  116. GUIContent recordIcon = new GUIContent(EditorBuiltin.GetAnimationWindowIcon(AnimationWindowIcon.Record),
  117. new LocEdString("Record"));
  118. GUIContent prevFrameIcon = new GUIContent(EditorBuiltin.GetAnimationWindowIcon(AnimationWindowIcon.FrameBack),
  119. new LocEdString("Previous frame"));
  120. GUIContent nextFrameIcon = new GUIContent(EditorBuiltin.GetAnimationWindowIcon(AnimationWindowIcon.FrameForward),
  121. new LocEdString("Next frame"));
  122. GUIContent addKeyframeIcon = new GUIContent(EditorBuiltin.GetAnimationWindowIcon(AnimationWindowIcon.AddKeyframe),
  123. new LocEdString("Add keyframe"));
  124. GUIContent addEventIcon = new GUIContent(EditorBuiltin.GetAnimationWindowIcon(AnimationWindowIcon.AddEvent),
  125. new LocEdString("Add event"));
  126. GUIContent optionsIcon = new GUIContent(EditorBuiltin.GetLibraryWindowIcon(LibraryWindowIcon.Options),
  127. new LocEdString("Options"));
  128. playButton = new GUIButton(playIcon);
  129. recordButton = new GUIButton(recordIcon);
  130. prevFrameButton = new GUIButton(prevFrameIcon);
  131. frameInputField = new GUIIntField();
  132. nextFrameButton = new GUIButton(nextFrameIcon);
  133. addKeyframeButton = new GUIButton(addKeyframeIcon);
  134. addEventButton = new GUIButton(addEventIcon);
  135. optionsButton = new GUIButton(optionsIcon);
  136. playButton.OnClick += () =>
  137. {
  138. // TODO
  139. // - Record current state of the scene object hierarchy
  140. // - Evaluate all curves manually and update them
  141. // - On end, restore original values of the scene object hierarchy
  142. };
  143. recordButton.OnClick += () =>
  144. {
  145. // TODO
  146. // - Every frame read back current values of all the current curve's properties and assign it to the current frame
  147. };
  148. prevFrameButton.OnClick += () =>
  149. {
  150. SetCurrentFrame(currentFrameIdx - 1);
  151. };
  152. frameInputField.OnChanged += SetCurrentFrame;
  153. nextFrameButton.OnClick += () =>
  154. {
  155. SetCurrentFrame(currentFrameIdx + 1);
  156. };
  157. addKeyframeButton.OnClick += () =>
  158. {
  159. guiCurveEditor.AddKeyFrameAtMarker();
  160. };
  161. addEventButton.OnClick += () =>
  162. {
  163. // TODO - Add event
  164. };
  165. optionsButton.OnClick += () =>
  166. {
  167. Vector2I openPosition = ScreenToWindowPos(Input.PointerPosition);
  168. AnimationOptions dropDown = DropDownWindow.Open<AnimationOptions>(this, openPosition);
  169. dropDown.Initialize(this);
  170. };
  171. // Property buttons
  172. addPropertyBtn = new GUIButton(new LocEdString("Add property"));
  173. delPropertyBtn = new GUIButton(new LocEdString("Delete selected"));
  174. addPropertyBtn.OnClick += () =>
  175. {
  176. Vector2I windowPos = ScreenToWindowPos(Input.PointerPosition);
  177. FieldSelectionWindow fieldSelection = DropDownWindow.Open<FieldSelectionWindow>(this, windowPos);
  178. fieldSelection.OnFieldSelected += OnFieldAdded;
  179. };
  180. delPropertyBtn.OnClick += () =>
  181. {
  182. LocEdString title = new LocEdString("Warning");
  183. LocEdString message = new LocEdString("Are you sure you want to remove all selected fields?");
  184. DialogBox.Open(title, message, DialogBox.Type.YesNo, x =>
  185. {
  186. if (x == DialogBox.ResultType.Yes)
  187. {
  188. RemoveSelectedFields();
  189. }
  190. });
  191. };
  192. GUILayout mainLayout = GUI.AddLayoutY();
  193. buttonLayout = mainLayout.AddLayoutX();
  194. buttonLayout.AddSpace(5);
  195. buttonLayout.AddElement(playButton);
  196. buttonLayout.AddElement(recordButton);
  197. buttonLayout.AddSpace(5);
  198. buttonLayout.AddElement(prevFrameButton);
  199. buttonLayout.AddElement(frameInputField);
  200. buttonLayout.AddElement(nextFrameButton);
  201. buttonLayout.AddSpace(5);
  202. buttonLayout.AddElement(addKeyframeButton);
  203. buttonLayout.AddElement(addEventButton);
  204. buttonLayout.AddSpace(5);
  205. buttonLayout.AddElement(optionsButton);
  206. buttonLayout.AddFlexibleSpace();
  207. buttonLayoutHeight = playButton.Bounds.height;
  208. GUILayout contentLayout = mainLayout.AddLayoutX();
  209. GUILayout fieldDisplayLayout = contentLayout.AddLayoutY(GUIOption.FixedWidth(FIELD_DISPLAY_WIDTH));
  210. guiFieldDisplay = new GUIAnimFieldDisplay(fieldDisplayLayout, FIELD_DISPLAY_WIDTH,
  211. Height - buttonLayoutHeight * 2, selectedSO);
  212. guiFieldDisplay.OnEntrySelected += OnFieldSelected;
  213. GUILayout bottomButtonLayout = fieldDisplayLayout.AddLayoutX();
  214. bottomButtonLayout.AddElement(addPropertyBtn);
  215. bottomButtonLayout.AddElement(delPropertyBtn);
  216. horzScrollBar = new GUIResizeableScrollBarH();
  217. horzScrollBar.OnScrollOrResize += OnHorzScrollOrResize;
  218. vertScrollBar = new GUIResizeableScrollBarV();
  219. vertScrollBar.OnScrollOrResize += OnVertScrollOrResize;
  220. GUILayout curveLayout = contentLayout.AddLayoutY();
  221. GUILayout curveLayoutHorz = curveLayout.AddLayoutX();
  222. GUILayout horzScrollBarLayout = curveLayout.AddLayoutX();
  223. horzScrollBarLayout.AddElement(horzScrollBar);
  224. horzScrollBarLayout.AddFlexibleSpace();
  225. editorPanel = curveLayoutHorz.AddPanel();
  226. curveLayoutHorz.AddElement(vertScrollBar);
  227. curveLayoutHorz.AddFlexibleSpace();
  228. scrollBarHeight = horzScrollBar.Bounds.height;
  229. scrollBarWidth = vertScrollBar.Bounds.width;
  230. Vector2I curveEditorSize = GetCurveEditorSize();
  231. guiCurveEditor = new GUICurveEditor(this, editorPanel, curveEditorSize.x, curveEditorSize.y);
  232. guiCurveEditor.OnFrameSelected += OnFrameSelected;
  233. guiCurveEditor.Redraw();
  234. horzScrollBar.SetWidth(curveEditorSize.x);
  235. vertScrollBar.SetHeight(curveEditorSize.y);
  236. SetCurrentFrame(currentFrameIdx);
  237. UpdateScrollBarSize();
  238. isInitialized = true;
  239. }
  240. private void ResizeGUI(int width, int height)
  241. {
  242. guiFieldDisplay.SetSize(FIELD_DISPLAY_WIDTH, height - buttonLayoutHeight * 2);
  243. Vector2I curveEditorSize = GetCurveEditorSize();
  244. guiCurveEditor.SetSize(curveEditorSize.x, curveEditorSize.y);
  245. guiCurveEditor.Redraw();
  246. horzScrollBar.SetWidth(curveEditorSize.x);
  247. vertScrollBar.SetHeight(curveEditorSize.y);
  248. UpdateScrollBarSize();
  249. UpdateScrollBarPosition();
  250. }
  251. #endregion
  252. #region Scroll, drag, zoom
  253. private Vector2I dragStartPos;
  254. private bool isButtonHeld;
  255. private bool isDragInProgress;
  256. private float zoomAmount;
  257. private void HandleDragAndZoomInput()
  258. {
  259. // Handle middle mouse dragging
  260. if (isDragInProgress)
  261. {
  262. float dragX = Input.GetAxisValue(InputAxis.MouseX) * DRAG_SCALE;
  263. float dragY = Input.GetAxisValue(InputAxis.MouseY) * DRAG_SCALE;
  264. Vector2 offset = guiCurveEditor.Offset;
  265. offset.x = Math.Max(0.0f, offset.x + dragX);
  266. offset.y += dragY;
  267. guiCurveEditor.Offset = offset;
  268. UpdateScrollBarSize();
  269. UpdateScrollBarPosition();
  270. }
  271. // Handle zoom in/out
  272. float scroll = Input.GetAxisValue(InputAxis.MouseZ);
  273. if (scroll != 0.0f)
  274. {
  275. Vector2I windowPos = ScreenToWindowPos(Input.PointerPosition);
  276. Vector2 curvePos;
  277. if (guiCurveEditor.WindowToCurveSpace(windowPos, out curvePos))
  278. {
  279. float zoom = scroll * ZOOM_SCALE;
  280. Zoom(curvePos, zoom);
  281. }
  282. }
  283. }
  284. private void SetVertScrollbarProperties(float position, float size)
  285. {
  286. Vector2 visibleRange = guiCurveEditor.Range;
  287. Vector2 totalRange = GetTotalRange();
  288. visibleRange.y = totalRange.y*size;
  289. guiCurveEditor.Range = visibleRange;
  290. float scrollableRange = totalRange.y - visibleRange.y;
  291. Vector2 offset = guiCurveEditor.Offset;
  292. offset.y = -scrollableRange * (position * 2.0f - 1.0f);
  293. guiCurveEditor.Offset = offset;
  294. }
  295. private void SetHorzScrollbarProperties(float position, float size)
  296. {
  297. Vector2 visibleRange = guiCurveEditor.Range;
  298. Vector2 totalRange = GetTotalRange();
  299. visibleRange.x = totalRange.x * size;
  300. guiCurveEditor.Range = visibleRange;
  301. float scrollableRange = totalRange.x - visibleRange.x;
  302. Vector2 offset = guiCurveEditor.Offset;
  303. offset.x = scrollableRange * position;
  304. guiCurveEditor.Offset = offset;
  305. }
  306. private void UpdateScrollBarSize()
  307. {
  308. Vector2 visibleRange = guiCurveEditor.Range;
  309. Vector2 totalRange = GetTotalRange();
  310. horzScrollBar.HandleSize = visibleRange.x / totalRange.x;
  311. vertScrollBar.HandleSize = visibleRange.y / totalRange.y;
  312. }
  313. private void UpdateScrollBarPosition()
  314. {
  315. Vector2 visibleRange = guiCurveEditor.Range;
  316. Vector2 totalRange = GetTotalRange();
  317. Vector2 scrollableRange = totalRange - visibleRange;
  318. Vector2 offset = guiCurveEditor.Offset;
  319. if (scrollableRange.x > 0.0f)
  320. horzScrollBar.Position = offset.x / scrollableRange.x;
  321. else
  322. horzScrollBar.Position = 0.0f;
  323. if (scrollableRange.y > 0.0f)
  324. {
  325. float pos = offset.y/scrollableRange.y;
  326. float sign = MathEx.Sign(pos);
  327. pos = sign*MathEx.Clamp01(MathEx.Abs(pos));
  328. pos = (1.0f - pos) /2.0f;
  329. vertScrollBar.Position = pos;
  330. }
  331. else
  332. vertScrollBar.Position = 0.0f;
  333. }
  334. private Vector2 GetZoomedRange()
  335. {
  336. float zoomLevel = MathEx.Pow(2, zoomAmount);
  337. Vector2 optimalRange = GetOptimalRange();
  338. return optimalRange / zoomLevel;
  339. }
  340. private Vector2 GetTotalRange()
  341. {
  342. // Return optimal range (that covers the visible curve)
  343. Vector2 optimalRange = GetOptimalRange();
  344. // Increase range in case user zoomed out
  345. Vector2 zoomedRange = GetZoomedRange();
  346. return Vector2.Max(optimalRange, zoomedRange);
  347. }
  348. private void Zoom(Vector2 curvePos, float amount)
  349. {
  350. // Increase or decrease the visible range depending on zoom level
  351. Vector2 oldZoomedRange = GetZoomedRange();
  352. zoomAmount = MathEx.Clamp(zoomAmount + amount, -10.0f, 10.0f);
  353. Vector2 zoomedRange = GetZoomedRange();
  354. Vector2 zoomedDiff = zoomedRange - oldZoomedRange;
  355. Vector2 currentRange = guiCurveEditor.Range;
  356. Vector2 newRange = currentRange + zoomedDiff;
  357. guiCurveEditor.Range = newRange;
  358. // When zooming, make sure to focus on the point provided, so adjust the offset
  359. Vector2 rangeScale = newRange;
  360. rangeScale.x /= currentRange.x;
  361. rangeScale.y /= currentRange.y;
  362. Vector2 relativeCurvePos = curvePos - guiCurveEditor.Offset;
  363. Vector2 newCurvePos = relativeCurvePos * rangeScale;
  364. Vector2 diff = newCurvePos - relativeCurvePos;
  365. guiCurveEditor.Offset -= diff;
  366. UpdateScrollBarSize();
  367. UpdateScrollBarPosition();
  368. }
  369. #endregion
  370. #region Curve save/load
  371. /// <summary>
  372. /// A set of animation curves for a field of a certain type.
  373. /// </summary>
  374. private struct FieldCurves
  375. {
  376. public SerializableProperty.FieldType type;
  377. public EdAnimationCurve[] curves;
  378. }
  379. [SerializeObject]
  380. private class EditorVector3CurveTangents
  381. {
  382. public string name;
  383. public TangentMode[] tangentsX;
  384. public TangentMode[] tangentsY;
  385. public TangentMode[] tangentsZ;
  386. }
  387. [SerializeObject]
  388. private class EditorFloatCurveTangents
  389. {
  390. public string name;
  391. public TangentMode[] tangents;
  392. }
  393. [SerializeObject]
  394. private class EditorCurveData
  395. {
  396. public EditorVector3CurveTangents[] positionCurves;
  397. public EditorVector3CurveTangents[] rotationCurves;
  398. public EditorVector3CurveTangents[] scaleCurves;
  399. public EditorFloatCurveTangents[] floatCurves;
  400. }
  401. private Dictionary<string, FieldCurves> curves = new Dictionary<string, FieldCurves>();
  402. private List<AnimationEvent> events = new List<AnimationEvent>();
  403. private bool clipIsImported;
  404. private void LoadFromClip(AnimationClip clip)
  405. {
  406. curves.Clear();
  407. selectedFields.Clear();
  408. clipIsImported = IsClipImported(clip);
  409. AnimationCurves clipCurves = clip.Curves;
  410. foreach (var curve in clipCurves.PositionCurves)
  411. {
  412. // TODO (don't forget tangents)
  413. // TODO - Trim last / if needed
  414. }
  415. events.AddRange(clip.Events);
  416. }
  417. private void SaveToClip(AnimationClip clip, string saveToPath)
  418. {
  419. if (!clipIsImported)
  420. {
  421. List<NamedVector3Curve> positionCurves = new List<NamedVector3Curve>();
  422. List<NamedVector3Curve> rotationCurves = new List<NamedVector3Curve>();
  423. List<NamedVector3Curve> scaleCurves = new List<NamedVector3Curve>();
  424. List<NamedFloatCurve> floatCurves = new List<NamedFloatCurve>();
  425. List<EditorVector3CurveTangents> positionTangents = new List<EditorVector3CurveTangents>();
  426. List<EditorVector3CurveTangents> rotationTangents = new List<EditorVector3CurveTangents>();
  427. List<EditorVector3CurveTangents> scaleTangents = new List<EditorVector3CurveTangents>();
  428. List<EditorFloatCurveTangents> floatTangents = new List<EditorFloatCurveTangents>();
  429. foreach (var kvp in curves)
  430. {
  431. string[] pathEntries = kvp.Key.Split('/');
  432. if (pathEntries.Length == 0)
  433. continue;
  434. string lastEntry = pathEntries[pathEntries.Length - 1];
  435. if (lastEntry == "Position" || lastEntry == "Rotation" || lastEntry == "Scale")
  436. {
  437. StringBuilder sb = new StringBuilder();
  438. for (int i = 0; i < pathEntries.Length - 2; i++)
  439. sb.Append(pathEntries[i] + "/");
  440. if (pathEntries.Length > 1)
  441. sb.Append(pathEntries[pathEntries.Length - 2]);
  442. string curvePath = sb.ToString();
  443. NamedVector3Curve curve = new NamedVector3Curve(curvePath,
  444. new AnimationCurve(kvp.Value.curves[0].KeyFrames),
  445. new AnimationCurve(kvp.Value.curves[1].KeyFrames),
  446. new AnimationCurve(kvp.Value.curves[2].KeyFrames));
  447. EditorVector3CurveTangents tangents = new EditorVector3CurveTangents();
  448. tangents.name = curvePath;
  449. tangents.tangentsX = kvp.Value.curves[0].TangentModes;
  450. tangents.tangentsY = kvp.Value.curves[1].TangentModes;
  451. tangents.tangentsZ = kvp.Value.curves[2].TangentModes;
  452. if (lastEntry == "Position")
  453. {
  454. positionCurves.Add(curve);
  455. positionTangents.Add(tangents);
  456. }
  457. else if (lastEntry == "Rotation")
  458. {
  459. rotationCurves.Add(curve);
  460. rotationTangents.Add(tangents);
  461. }
  462. else if (lastEntry == "Scale")
  463. {
  464. scaleCurves.Add(curve);
  465. scaleTangents.Add(tangents);
  466. }
  467. }
  468. else
  469. {
  470. NamedFloatCurve curve = new NamedFloatCurve(kvp.Key,
  471. new AnimationCurve(kvp.Value.curves[0].KeyFrames));
  472. EditorFloatCurveTangents tangents = new EditorFloatCurveTangents();
  473. tangents.name = kvp.Key;
  474. tangents.tangents = kvp.Value.curves[0].TangentModes;
  475. floatCurves.Add(curve);
  476. floatTangents.Add(tangents);
  477. }
  478. }
  479. AnimationCurves newClipCurves = new AnimationCurves();
  480. newClipCurves.PositionCurves = positionCurves.ToArray();
  481. newClipCurves.RotationCurves = rotationCurves.ToArray();
  482. newClipCurves.ScaleCurves = scaleCurves.ToArray();
  483. newClipCurves.FloatCurves = floatCurves.ToArray();
  484. clip.Curves = newClipCurves;
  485. clip.Events = events.ToArray();
  486. string resourcePath = ProjectLibrary.GetPath(clip);
  487. if (string.IsNullOrEmpty(resourcePath))
  488. ProjectLibrary.Create(clip, saveToPath);
  489. else
  490. ProjectLibrary.Save(clip);
  491. // Save tangents for editor only use
  492. LibraryEntry entry = ProjectLibrary.GetEntry(resourcePath);
  493. string clipName = PathEx.GetTail(resourcePath);
  494. if (entry != null && entry.Type == LibraryEntryType.File)
  495. {
  496. FileEntry fileEntry = (FileEntry)entry;
  497. ResourceMeta[] metas = fileEntry.ResourceMetas;
  498. for (int i = 0; i < metas.Length; i++)
  499. {
  500. if (clipName == metas[i].SubresourceName)
  501. {
  502. EditorCurveData newCurveData = new EditorCurveData();
  503. newCurveData.positionCurves = positionTangents.ToArray();
  504. newCurveData.rotationCurves = rotationTangents.ToArray();
  505. newCurveData.scaleCurves = scaleTangents.ToArray();
  506. newCurveData.floatCurves = floatTangents.ToArray();
  507. ProjectLibrary.SetEditorData(resourcePath, newCurveData);
  508. break;
  509. }
  510. }
  511. }
  512. }
  513. else
  514. {
  515. string resourcePath = ProjectLibrary.GetPath(clip);
  516. LibraryEntry entry = ProjectLibrary.GetEntry(resourcePath);
  517. if (entry != null && entry.Type == LibraryEntryType.File)
  518. {
  519. FileEntry fileEntry = (FileEntry) entry;
  520. MeshImportOptions meshImportOptions = (MeshImportOptions)fileEntry.Options;
  521. string clipName = PathEx.GetTail(resourcePath);
  522. List<ImportedAnimationEvents> newEvents = new List<ImportedAnimationEvents>();
  523. newEvents.AddRange(meshImportOptions.AnimationEvents);
  524. bool isExisting = false;
  525. for (int i = 0; i < newEvents.Count; i++)
  526. {
  527. if (newEvents[i].name == clipName)
  528. {
  529. newEvents[i].events = events.ToArray();
  530. isExisting = true;
  531. break;
  532. }
  533. }
  534. if (!isExisting)
  535. {
  536. ImportedAnimationEvents newEntry = new ImportedAnimationEvents();
  537. newEntry.name = clipName;
  538. newEntry.events = events.ToArray();
  539. newEvents.Add(newEntry);
  540. }
  541. meshImportOptions.AnimationEvents = newEvents.ToArray();
  542. ProjectLibrary.Reimport(resourcePath, meshImportOptions, true);
  543. }
  544. }
  545. }
  546. private bool IsClipImported(AnimationClip clip)
  547. {
  548. string resourcePath = ProjectLibrary.GetPath(clip);
  549. return ProjectLibrary.IsSubresource(resourcePath);
  550. }
  551. #endregion
  552. #region Curve display
  553. private int currentFrameIdx;
  554. private int fps = 1;
  555. internal int FPS
  556. {
  557. get { return fps; }
  558. set { guiCurveEditor.SetFPS(value); fps = MathEx.Max(value, 1); }
  559. }
  560. private void SetCurrentFrame(int frameIdx)
  561. {
  562. currentFrameIdx = Math.Max(0, frameIdx);
  563. frameInputField.Value = currentFrameIdx;
  564. guiCurveEditor.SetMarkedFrame(currentFrameIdx);
  565. float time = guiCurveEditor.GetTimeForFrame(currentFrameIdx);
  566. List<GUIAnimFieldPathValue> values = new List<GUIAnimFieldPathValue>();
  567. foreach (var kvp in curves)
  568. {
  569. SerializableProperty property = GUIAnimFieldDisplay.FindProperty(selectedSO, kvp.Key);
  570. if (property != null)
  571. {
  572. GUIAnimFieldPathValue fieldValue = new GUIAnimFieldPathValue();
  573. fieldValue.path = kvp.Key;
  574. switch (kvp.Value.type)
  575. {
  576. case SerializableProperty.FieldType.Vector2:
  577. {
  578. Vector2 value = new Vector2();
  579. for (int i = 0; i < 2; i++)
  580. value[i] = kvp.Value.curves[i].Evaluate(time, false);
  581. fieldValue.value = value;
  582. }
  583. break;
  584. case SerializableProperty.FieldType.Vector3:
  585. {
  586. Vector3 value = new Vector3();
  587. for (int i = 0; i < 3; i++)
  588. value[i] = kvp.Value.curves[i].Evaluate(time, false);
  589. fieldValue.value = value;
  590. }
  591. break;
  592. case SerializableProperty.FieldType.Vector4:
  593. {
  594. Vector4 value = new Vector4();
  595. for (int i = 0; i < 4; i++)
  596. value[i] = kvp.Value.curves[i].Evaluate(time, false);
  597. fieldValue.value = value;
  598. }
  599. break;
  600. case SerializableProperty.FieldType.Color:
  601. {
  602. Color value = new Color();
  603. for (int i = 0; i < 4; i++)
  604. value[i] = kvp.Value.curves[i].Evaluate(time, false);
  605. fieldValue.value = value;
  606. }
  607. break;
  608. case SerializableProperty.FieldType.Bool:
  609. case SerializableProperty.FieldType.Int:
  610. case SerializableProperty.FieldType.Float:
  611. fieldValue.value = kvp.Value.curves[0].Evaluate(time, false); ;
  612. break;
  613. }
  614. values.Add(fieldValue);
  615. }
  616. }
  617. guiFieldDisplay.SetDisplayValues(values.ToArray());
  618. }
  619. private Vector2 GetOptimalRange()
  620. {
  621. List<EdAnimationCurve> displayedCurves = new List<EdAnimationCurve>();
  622. for (int i = 0; i < selectedFields.Count; i++)
  623. {
  624. EdAnimationCurve curve;
  625. if (TryGetCurve(selectedFields[i], out curve))
  626. displayedCurves.Add(curve);
  627. }
  628. float xRange;
  629. float yRange;
  630. CalculateRange(displayedCurves, out xRange, out yRange);
  631. // Add padding to y range
  632. yRange *= 1.05f;
  633. // Don't allow zero range
  634. if (xRange == 0.0f)
  635. xRange = 60.0f;
  636. if (yRange == 0.0f)
  637. yRange = 10.0f;
  638. return new Vector2(xRange, yRange);
  639. }
  640. private void UpdateDisplayedCurves()
  641. {
  642. List<EdAnimationCurve> curvesToDisplay = new List<EdAnimationCurve>();
  643. for (int i = 0; i < selectedFields.Count; i++)
  644. {
  645. EdAnimationCurve curve;
  646. if (TryGetCurve(selectedFields[i], out curve))
  647. curvesToDisplay.Add(curve);
  648. }
  649. guiCurveEditor.SetCurves(curvesToDisplay.ToArray());
  650. Vector2 newRange = GetOptimalRange();
  651. // Don't reduce visible range
  652. newRange.x = Math.Max(newRange.x, guiCurveEditor.Range.x);
  653. newRange.y = Math.Max(newRange.y, guiCurveEditor.Range.y);
  654. guiCurveEditor.Range = newRange;
  655. UpdateScrollBarSize();
  656. }
  657. #endregion
  658. #region Field display
  659. private List<string> selectedFields = new List<string>();
  660. private void AddNewField(string path, SerializableProperty.FieldType type)
  661. {
  662. guiFieldDisplay.AddField(path);
  663. switch (type)
  664. {
  665. case SerializableProperty.FieldType.Vector4:
  666. {
  667. FieldCurves fieldCurves = new FieldCurves();
  668. fieldCurves.type = type;
  669. fieldCurves.curves = new EdAnimationCurve[4];
  670. string[] subPaths = { ".x", ".y", ".z", ".w" };
  671. for (int i = 0; i < subPaths.Length; i++)
  672. {
  673. string subFieldPath = path + subPaths[i];
  674. fieldCurves.curves[i] = new EdAnimationCurve();
  675. selectedFields.Add(subFieldPath);
  676. }
  677. curves[path] = fieldCurves;
  678. }
  679. break;
  680. case SerializableProperty.FieldType.Vector3:
  681. {
  682. FieldCurves fieldCurves = new FieldCurves();
  683. fieldCurves.type = type;
  684. fieldCurves.curves = new EdAnimationCurve[3];
  685. string[] subPaths = { ".x", ".y", ".z" };
  686. for (int i = 0; i < subPaths.Length; i++)
  687. {
  688. string subFieldPath = path + subPaths[i];
  689. fieldCurves.curves[i] = new EdAnimationCurve();
  690. selectedFields.Add(subFieldPath);
  691. }
  692. curves[path] = fieldCurves;
  693. }
  694. break;
  695. case SerializableProperty.FieldType.Vector2:
  696. {
  697. FieldCurves fieldCurves = new FieldCurves();
  698. fieldCurves.type = type;
  699. fieldCurves.curves = new EdAnimationCurve[2];
  700. string[] subPaths = { ".x", ".y" };
  701. for (int i = 0; i < subPaths.Length; i++)
  702. {
  703. string subFieldPath = path + subPaths[i];
  704. fieldCurves.curves[i] = new EdAnimationCurve();
  705. selectedFields.Add(subFieldPath);
  706. }
  707. curves[path] = fieldCurves;
  708. }
  709. break;
  710. case SerializableProperty.FieldType.Color:
  711. {
  712. FieldCurves fieldCurves = new FieldCurves();
  713. fieldCurves.type = type;
  714. fieldCurves.curves = new EdAnimationCurve[4];
  715. string[] subPaths = { ".r", ".g", ".b", ".a" };
  716. for (int i = 0; i < subPaths.Length; i++)
  717. {
  718. string subFieldPath = path + subPaths[i];
  719. fieldCurves.curves[i] = new EdAnimationCurve();
  720. selectedFields.Add(subFieldPath);
  721. }
  722. curves[path] = fieldCurves;
  723. }
  724. break;
  725. default: // Primitive type
  726. {
  727. FieldCurves fieldCurves = new FieldCurves();
  728. fieldCurves.type = type;
  729. fieldCurves.curves = new EdAnimationCurve[1];
  730. fieldCurves.curves[0] = new EdAnimationCurve();
  731. selectedFields.Add(path);
  732. curves[path] = fieldCurves;
  733. }
  734. break;
  735. }
  736. UpdateDisplayedCurves();
  737. }
  738. private void SelectField(string path, bool additive)
  739. {
  740. if (!additive)
  741. selectedFields.Clear();
  742. if (!string.IsNullOrEmpty(path))
  743. {
  744. selectedFields.RemoveAll(x => { return x == path || IsPathParent(x, path); });
  745. selectedFields.Add(path);
  746. }
  747. guiFieldDisplay.SetSelection(selectedFields.ToArray());
  748. UpdateDisplayedCurves();
  749. }
  750. private void RemoveSelectedFields()
  751. {
  752. for (int i = 0; i < selectedFields.Count; i++)
  753. {
  754. selectedFields.Remove(selectedFields[i]);
  755. curves.Remove(GetSubPathParent(selectedFields[i]));
  756. }
  757. List<string> existingFields = new List<string>();
  758. foreach (var KVP in curves)
  759. existingFields.Add(KVP.Key);
  760. guiFieldDisplay.SetFields(existingFields.ToArray());
  761. selectedFields.Clear();
  762. UpdateDisplayedCurves();
  763. }
  764. #endregion
  765. #region Helpers
  766. private Vector2I GetCurveEditorSize()
  767. {
  768. Vector2I output = new Vector2I();
  769. output.x = Math.Max(0, Width - FIELD_DISPLAY_WIDTH - scrollBarWidth);
  770. output.y = Math.Max(0, Height - buttonLayoutHeight - scrollBarHeight);
  771. return output;
  772. }
  773. private static void CalculateRange(List<EdAnimationCurve> curves, out float xRange, out float yRange)
  774. {
  775. xRange = 0.0f;
  776. yRange = 0.0f;
  777. foreach (var curve in curves)
  778. {
  779. KeyFrame[] keyframes = curve.KeyFrames;
  780. foreach (var key in keyframes)
  781. {
  782. xRange = Math.Max(xRange, key.time);
  783. yRange = Math.Max(yRange, Math.Abs(key.value));
  784. }
  785. }
  786. }
  787. private bool TryGetCurve(string path, out EdAnimationCurve curve)
  788. {
  789. int index = path.LastIndexOf(".");
  790. string parentPath;
  791. string subPathSuffix = null;
  792. if (index == -1)
  793. {
  794. parentPath = path;
  795. }
  796. else
  797. {
  798. parentPath = path.Substring(0, index);
  799. subPathSuffix = path.Substring(index, path.Length - index);
  800. }
  801. FieldCurves fieldCurves;
  802. if (curves.TryGetValue(parentPath, out fieldCurves))
  803. {
  804. if (!string.IsNullOrEmpty(subPathSuffix))
  805. {
  806. if (subPathSuffix == ".x" || subPathSuffix == ".r")
  807. {
  808. curve = fieldCurves.curves[0];
  809. return true;
  810. }
  811. else if (subPathSuffix == ".y" || subPathSuffix == ".g")
  812. {
  813. curve = fieldCurves.curves[1];
  814. return true;
  815. }
  816. else if (subPathSuffix == ".z" || subPathSuffix == ".b")
  817. {
  818. curve = fieldCurves.curves[2];
  819. return true;
  820. }
  821. else if (subPathSuffix == ".w" || subPathSuffix == ".a")
  822. {
  823. curve = fieldCurves.curves[3];
  824. return true;
  825. }
  826. }
  827. else
  828. {
  829. curve = fieldCurves.curves[0];
  830. return true;
  831. }
  832. }
  833. curve = null;
  834. return false;
  835. }
  836. private bool IsPathParent(string child, string parent)
  837. {
  838. string[] childEntries = child.Split('/', '.');
  839. string[] parentEntries = parent.Split('/', '.');
  840. if (parentEntries.Length >= child.Length)
  841. return false;
  842. int compareLength = Math.Min(childEntries.Length, parentEntries.Length);
  843. for (int i = 0; i < compareLength; i++)
  844. {
  845. if (childEntries[i] != parentEntries[i])
  846. return false;
  847. }
  848. return true;
  849. }
  850. private string GetSubPathParent(string path)
  851. {
  852. int index = path.LastIndexOf(".");
  853. if (index == -1)
  854. return path;
  855. return path.Substring(0, index);
  856. }
  857. #endregion
  858. #region Input callbacks
  859. private void OnPointerPressed(PointerEvent ev)
  860. {
  861. if (!isInitialized)
  862. return;
  863. guiCurveEditor.OnPointerPressed(ev);
  864. if (ev.button == PointerButton.Middle)
  865. {
  866. Vector2I windowPos = ScreenToWindowPos(ev.ScreenPos);
  867. Vector2 curvePos;
  868. if (guiCurveEditor.WindowToCurveSpace(windowPos, out curvePos))
  869. {
  870. dragStartPos = windowPos;
  871. isButtonHeld = true;
  872. }
  873. }
  874. }
  875. private void OnPointerMoved(PointerEvent ev)
  876. {
  877. if (!isInitialized)
  878. return;
  879. guiCurveEditor.OnPointerMoved(ev);
  880. if (isButtonHeld)
  881. {
  882. Vector2I windowPos = ScreenToWindowPos(ev.ScreenPos);
  883. int distance = Vector2I.Distance(dragStartPos, windowPos);
  884. if (distance >= DRAG_START_DISTANCE)
  885. {
  886. isDragInProgress = true;
  887. Cursor.Hide();
  888. Rect2I clipRect;
  889. clipRect.x = ev.ScreenPos.x - 2;
  890. clipRect.y = ev.ScreenPos.y - 2;
  891. clipRect.width = 4;
  892. clipRect.height = 4;
  893. Cursor.ClipToRect(clipRect);
  894. }
  895. }
  896. }
  897. private void OnPointerReleased(PointerEvent ev)
  898. {
  899. if (isDragInProgress)
  900. {
  901. Cursor.Show();
  902. Cursor.ClipDisable();
  903. }
  904. isButtonHeld = false;
  905. isDragInProgress = false;
  906. if (!isInitialized)
  907. return;
  908. guiCurveEditor.OnPointerReleased(ev);
  909. }
  910. private void OnButtonUp(ButtonEvent ev)
  911. {
  912. if (!isInitialized)
  913. return;
  914. guiCurveEditor.OnButtonUp(ev);
  915. }
  916. #endregion
  917. #region General callbacks
  918. private void OnFieldAdded(string path, SerializableProperty.FieldType type)
  919. {
  920. AddNewField(path, type);
  921. }
  922. private void OnHorzScrollOrResize(float position, float size)
  923. {
  924. SetHorzScrollbarProperties(position, size);
  925. }
  926. private void OnVertScrollOrResize(float position, float size)
  927. {
  928. SetVertScrollbarProperties(position, size);
  929. }
  930. private void OnFieldSelected(string path)
  931. {
  932. bool additive = Input.IsButtonHeld(ButtonCode.LeftShift) || Input.IsButtonHeld(ButtonCode.RightShift);
  933. SelectField(path, additive);
  934. }
  935. private void OnSelectionChanged(SceneObject[] sceneObjects, string[] resourcePaths)
  936. {
  937. RebuildGUI();
  938. }
  939. private void OnFrameSelected(int frameIdx)
  940. {
  941. SetCurrentFrame(frameIdx);
  942. }
  943. #endregion
  944. }
  945. /// <summary>
  946. /// Drop down window that displays options used by the animation window.
  947. /// </summary>
  948. [DefaultSize(100, 50)]
  949. internal class AnimationOptions : DropDownWindow
  950. {
  951. /// <summary>
  952. /// Initializes the drop down window by creating the necessary GUI. Must be called after construction and before
  953. /// use.
  954. /// </summary>
  955. /// <param name="parent">Animation window that this drop down window is a part of.</param>
  956. internal void Initialize(AnimationWindow parent)
  957. {
  958. GUIIntField fpsField = new GUIIntField(new LocEdString("FPS"), 40);
  959. fpsField.Value = parent.FPS;
  960. fpsField.OnChanged += x => { parent.FPS = x; };
  961. GUILayoutY vertLayout = GUI.AddLayoutY();
  962. vertLayout.AddFlexibleSpace();
  963. GUILayoutX contentLayout = vertLayout.AddLayoutX();
  964. contentLayout.AddFlexibleSpace();
  965. contentLayout.AddElement(fpsField);
  966. contentLayout.AddFlexibleSpace();
  967. vertLayout.AddFlexibleSpace();
  968. }
  969. }
  970. /** @} */
  971. }