GUICurveDrawing.cs 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700
  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 BansheeEngine;
  6. namespace BansheeEditor
  7. {
  8. /** @addtogroup AnimationEditor
  9. * @{
  10. */
  11. /// <summary>
  12. /// Draws one or multiple curves over the specified physical area. User can specify horizontal and vertical range to
  13. /// display, as well as physical size of the GUI area.
  14. /// </summary>
  15. internal class GUICurveDrawing
  16. {
  17. private const int LINE_SPLIT_WIDTH = 2;
  18. private const int TANGENT_LINE_DISTANCE = 30;
  19. private static readonly Color COLOR_MID_GRAY = new Color(90.0f / 255.0f, 90.0f / 255.0f, 90.0f / 255.0f, 1.0f);
  20. private static readonly Color COLOR_DARK_GRAY = new Color(40.0f / 255.0f, 40.0f / 255.0f, 40.0f / 255.0f, 1.0f);
  21. private EdAnimationCurve[] curves;
  22. private bool[][] selectedKeyframes;
  23. private int width;
  24. private int height;
  25. private float xRange = 60.0f;
  26. private float yRange = 20.0f;
  27. private int fps = 1;
  28. private int markedFrameIdx = 0;
  29. private int drawableWidth;
  30. private GUICanvas canvas;
  31. private GUIGraphTicks tickHandler;
  32. /// <summary>
  33. /// Creates a new curve drawing GUI element.
  34. /// </summary>
  35. /// <param name="layout">Layout into which to add the GUI element.</param>
  36. /// <param name="width">Width of the element in pixels.</param>
  37. /// <param name="height">Height of the element in pixels.</param>
  38. /// <param name="curves">Initial set of curves to display. </param>
  39. public GUICurveDrawing(GUILayout layout, int width, int height, EdAnimationCurve[] curves)
  40. {
  41. canvas = new GUICanvas();
  42. layout.AddElement(canvas);
  43. tickHandler = new GUIGraphTicks(GUITickStepType.Time);
  44. this.curves = curves;
  45. SetSize(width, height);
  46. ClearSelectedKeyframes(); // Makes sure the array is initialized
  47. Rebuild();
  48. }
  49. /// <summary>
  50. /// Change the set of curves to display.
  51. /// </summary>
  52. /// <param name="curves">New set of curves to draw on the GUI element.</param>
  53. public void SetCurves(EdAnimationCurve[] curves)
  54. {
  55. this.curves = curves;
  56. }
  57. /// <summary>
  58. /// Change the physical size of the GUI element.
  59. /// </summary>
  60. /// <param name="width">Width of the element in pixels.</param>
  61. /// <param name="height">Height of the element in pixels.</param>
  62. public void SetSize(int width, int height)
  63. {
  64. this.width = width;
  65. this.height = height;
  66. canvas.SetWidth(width);
  67. canvas.SetHeight(height);
  68. drawableWidth = Math.Max(0, width - GUIGraphTime.PADDING * 2);
  69. }
  70. /// <summary>
  71. /// Changes the visible range that the GUI element displays.
  72. /// </summary>
  73. /// <param name="xRange">Range of the horizontal area. Displayed area will range from [0, xRange].</param>
  74. /// <param name="yRange">Range of the vertical area. Displayed area will range from
  75. /// [-yRange * 0.5, yRange * 0.5]</param>
  76. public void SetRange(float xRange, float yRange)
  77. {
  78. this.xRange = xRange;
  79. this.yRange = yRange;
  80. }
  81. /// <summary>
  82. /// Number of frames per second, used for frame selection and marking.
  83. /// </summary>
  84. /// <param name="fps">Number of prames per second.</param>
  85. public void SetFPS(int fps)
  86. {
  87. this.fps = Math.Max(1, fps);
  88. }
  89. /// <summary>
  90. /// Sets the frame at which to display the frame marker.
  91. /// </summary>
  92. /// <param name="frameIdx">Index of the frame to display the marker on, or -1 to clear the marker.</param>
  93. public void SetMarkedFrame(int frameIdx)
  94. {
  95. markedFrameIdx = frameIdx;
  96. }
  97. /// <summary>
  98. /// Marks the specified key-frame as selected, changing the way it is displayed.
  99. /// </summary>
  100. /// <param name="keyframeRef">Keyframe reference containing the curve and keyframe index.</param>
  101. /// <param name="selected">True to select it, false to deselect it.</param>
  102. public void SelectKeyframe(KeyframeRef keyframeRef, bool selected)
  103. {
  104. if (selectedKeyframes == null)
  105. return;
  106. if (keyframeRef.curveIdx < 0 || keyframeRef.curveIdx >= selectedKeyframes.Length)
  107. return;
  108. if (keyframeRef.keyIdx < 0 || keyframeRef.keyIdx >= selectedKeyframes[keyframeRef.curveIdx].Length)
  109. return;
  110. selectedKeyframes[keyframeRef.curveIdx][keyframeRef.keyIdx] = selected;
  111. }
  112. /// <summary>
  113. /// Clears any key-frames that were marked as selected.
  114. /// </summary>
  115. public void ClearSelectedKeyframes()
  116. {
  117. selectedKeyframes = new bool[curves.Length][];
  118. for (int i = 0; i < curves.Length; i++)
  119. {
  120. KeyFrame[] keyframes = curves[i].KeyFrames;
  121. selectedKeyframes[i] = new bool[keyframes.Length];
  122. }
  123. }
  124. /// <summary>
  125. /// Returns time for a frame with the specified index. Depends on set range and FPS.
  126. /// </summary>
  127. /// <param name="frameIdx">Index of the frame (not a key-frame) to get the time for.</param>
  128. /// <returns>Time of the frame with the provided index. </returns>
  129. public float GetTimeForFrame(int frameIdx)
  130. {
  131. float range = GetRange();
  132. int numFrames = (int)range * fps;
  133. float timePerFrame = range / numFrames;
  134. return frameIdx* timePerFrame;
  135. }
  136. /// <summary>
  137. /// Attempts to find a keyframe under the provided coordinates.
  138. /// </summary>
  139. /// <param name="pixelCoords">Coordinates relative to this GUI element in pixels.</param>
  140. /// <param name="keyframe">Output object containing keyframe index and index of the curve it belongs to. Only valid
  141. /// if method returns true.</param>
  142. /// <returns>True if there is a keyframe under the coordinates, false otherwise.</returns>
  143. public bool FindKeyFrame(Vector2I pixelCoords, out KeyframeRef keyframe)
  144. {
  145. keyframe = new KeyframeRef();
  146. float nearestDistance = float.MaxValue;
  147. for (int i = 0; i < curves.Length; i++)
  148. {
  149. EdAnimationCurve curve = curves[i];
  150. KeyFrame[] keyframes = curve.KeyFrames;
  151. for (int j = 0; j < keyframes.Length; j++)
  152. {
  153. Vector2 keyframeCurveCoords = new Vector2(keyframes[j].time, keyframes[j].value);
  154. Vector2I keyframeCoords = CurveToPixelSpace(keyframeCurveCoords);
  155. float distanceToKey = Vector2I.Distance(pixelCoords, keyframeCoords);
  156. if (distanceToKey < nearestDistance)
  157. {
  158. nearestDistance = distanceToKey;
  159. keyframe.keyIdx = j;
  160. keyframe.curveIdx = i;
  161. }
  162. }
  163. }
  164. // We're not near any keyframe
  165. if (nearestDistance > 5.0f)
  166. return false;
  167. return true;
  168. }
  169. /// <summary>
  170. /// Attempts to find a a tangent handle under the provided coordinates.
  171. /// </summary>
  172. /// <param name="pixelCoords">Coordinates relative to this GUI element in pixels.</param>
  173. /// <param name="tangent">Output object containing keyframe information and tangent type. Only valid if method
  174. /// returns true.</param>
  175. /// <returns>True if there is a tangent handle under the coordinates, false otherwise.</returns>
  176. public bool FindTangent(Vector2I pixelCoords, out TangentRef tangent)
  177. {
  178. tangent = new TangentRef();
  179. float nearestDistance = float.MaxValue;
  180. for (int i = 0; i < curves.Length; i++)
  181. {
  182. EdAnimationCurve curve = curves[i];
  183. KeyFrame[] keyframes = curve.KeyFrames;
  184. for (int j = 0; j < keyframes.Length; j++)
  185. {
  186. if (!IsSelected(i, j))
  187. continue;
  188. TangentMode tangentMode = curve.TangentModes[j];
  189. if (IsTangentDisplayed(tangentMode, TangentType.In))
  190. {
  191. Vector2I tangentCoords = GetTangentPosition(keyframes[j], TangentType.In);
  192. float distanceToHandle = Vector2I.Distance(pixelCoords, tangentCoords);
  193. if (distanceToHandle < nearestDistance)
  194. {
  195. nearestDistance = distanceToHandle;
  196. tangent.keyframeRef.keyIdx = j;
  197. tangent.keyframeRef.curveIdx = i;
  198. tangent.type = TangentType.In;
  199. }
  200. ; }
  201. if (IsTangentDisplayed(tangentMode, TangentType.Out))
  202. {
  203. Vector2I tangentCoords = GetTangentPosition(keyframes[j], TangentType.Out);
  204. float distanceToHandle = Vector2I.Distance(pixelCoords, tangentCoords);
  205. if (distanceToHandle < nearestDistance)
  206. {
  207. nearestDistance = distanceToHandle;
  208. tangent.keyframeRef.keyIdx = j;
  209. tangent.keyframeRef.curveIdx = i;
  210. tangent.type = TangentType.Out;
  211. }
  212. }
  213. }
  214. }
  215. // We're not near any keyframe
  216. if (nearestDistance > 5.0f)
  217. return false;
  218. return true;
  219. }
  220. /// <summary>
  221. /// Converts pixel coordinates into coordinates in curve space.
  222. /// </summary>
  223. /// <param name="pixelCoords">Coordinates relative to this GUI element, in pixels.</param>
  224. /// <param name="curveCoords">Curve coordinates within the range as specified by <see cref="SetRange"/>. Only
  225. /// valid when function returns true.</param>
  226. /// <returns>True if the window coordinates were within the curve area, false otherwise.</returns>
  227. public bool PixelToCurveSpace(Vector2I pixelCoords, out Vector2 curveCoords)
  228. {
  229. Rect2I bounds = canvas.Bounds;
  230. // Check if outside of curve drawing bounds
  231. if (pixelCoords.x < (bounds.x + GUIGraphTime.PADDING) || pixelCoords.x >= (bounds.x + bounds.width - GUIGraphTime.PADDING) ||
  232. pixelCoords.y < bounds.y || pixelCoords.y >= (bounds.y + bounds.height))
  233. {
  234. curveCoords = new Vector2();
  235. return false;
  236. }
  237. // Find time and value of the place under the coordinates
  238. Vector2I relativeCoords = pixelCoords - new Vector2I(bounds.x + GUIGraphTime.PADDING, bounds.y);
  239. float lengthPerPixel = GetRange() / drawableWidth;
  240. float heightPerPixel = yRange / height;
  241. float yOffset = yRange / 2.0f;
  242. float t = relativeCoords.x * lengthPerPixel;
  243. float value = yOffset - relativeCoords.y * heightPerPixel;
  244. curveCoords = new Vector2();
  245. curveCoords.x = t;
  246. curveCoords.y = value;
  247. return true;
  248. }
  249. /// <summary>
  250. /// Converts coordinate in curve space (time, value) into pixel coordinates relative to this element's origin.
  251. /// </summary>
  252. /// <param name="curveCoords">Time and value of the location to convert.</param>
  253. /// <returns>Coordinates relative to this element's origin, in pixels.</returns>
  254. public Vector2I CurveToPixelSpace(Vector2 curveCoords)
  255. {
  256. int heightOffset = height / 2; // So that y = 0 is at center of canvas
  257. Vector2I pixelCoords = new Vector2I();
  258. pixelCoords.x = (int)((curveCoords.x / GetRange()) * drawableWidth) + GUIGraphTime.PADDING;
  259. pixelCoords.y = heightOffset - (int)((curveCoords.y / yRange) * height);
  260. return pixelCoords;
  261. }
  262. /// <summary>
  263. /// Draws a vertical frame marker on the curve area.
  264. /// </summary>
  265. /// <param name="t">Time at which to draw the marker.</param>
  266. /// <param name="color">Color with which to draw the marker.</param>
  267. /// <param name="onTop">Determines should the marker be drawn above or below the curve.</param>
  268. private void DrawFrameMarker(float t, Color color, bool onTop)
  269. {
  270. int xPos = (int)((t / GetRange()) * drawableWidth) + GUIGraphTime.PADDING;
  271. Vector2I start = new Vector2I(xPos, 0);
  272. Vector2I end = new Vector2I(xPos, height);
  273. byte depth;
  274. if (onTop)
  275. depth = 110;
  276. else
  277. depth = 128;
  278. canvas.DrawLine(start, end, color, depth);
  279. }
  280. /// <summary>
  281. /// Draws a horizontal line representing the line at y = 0.
  282. /// </summary>
  283. private void DrawCenterLine()
  284. {
  285. int heightOffset = height / 2; // So that y = 0 is at center of canvas
  286. Vector2I start = new Vector2I(0, heightOffset);
  287. Vector2I end = new Vector2I(width, heightOffset);
  288. canvas.DrawLine(start, end, COLOR_DARK_GRAY);
  289. }
  290. /// <summary>
  291. /// Draws a diamond shape of the specified size at the coordinates.
  292. /// </summary>
  293. /// <param name="center">Position at which to place the diamond's center, in pixel coordinates.</param>
  294. /// <param name="size">Determines number of pixels to extend the diamond in each direction.</param>
  295. /// <param name="innerColor">Color of the diamond's background.</param>
  296. /// <param name="outerColor">Color of the diamond's outline.</param>
  297. private void DrawDiamond(Vector2I center, int size, Color innerColor, Color outerColor)
  298. {
  299. Vector2I a = new Vector2I(center.x - size, center.y);
  300. Vector2I b = new Vector2I(center.x, center.y - size);
  301. Vector2I c = new Vector2I(center.x + size, center.y);
  302. Vector2I d = new Vector2I(center.x, center.y + size);
  303. // Draw diamond shape
  304. Vector2I[] linePoints = new Vector2I[] { a, b, c, d, a };
  305. Vector2I[] trianglePoints = new Vector2I[] { b, c, a, d };
  306. canvas.DrawTriangleStrip(trianglePoints, innerColor, 101);
  307. canvas.DrawPolyLine(linePoints, outerColor, 100);
  308. }
  309. /// <summary>
  310. /// Draws a keyframe a the specified time and value.
  311. /// </summary>
  312. /// <param name="t">Time to draw the keyframe at.</param>
  313. /// <param name="y">Y value to draw the keyframe at.</param>
  314. /// <param name="selected">Determines should the keyframe be drawing using the selected color scheme, or normally.
  315. /// </param>
  316. private void DrawKeyframe(float t, float y, bool selected)
  317. {
  318. Vector2I pixelCoords = CurveToPixelSpace(new Vector2(t, y));
  319. if (selected)
  320. DrawDiamond(pixelCoords, 3, Color.White, Color.BansheeOrange);
  321. else
  322. DrawDiamond(pixelCoords, 3, Color.White, Color.Black);
  323. }
  324. /// <summary>
  325. /// Draws zero, one or two tangents for the specified keyframe. Whether tangents are drawn depends on the provided
  326. /// mode.
  327. /// </summary>
  328. /// <param name="keyFrame">Keyframe to draw the tangents for.</param>
  329. /// <param name="tangentMode">Type of tangents in the keyframe.</param>
  330. private void DrawTangents(KeyFrame keyFrame, TangentMode tangentMode)
  331. {
  332. Vector2I keyframeCoords = CurveToPixelSpace(new Vector2(keyFrame.time, keyFrame.value));
  333. if (IsTangentDisplayed(tangentMode, TangentType.In))
  334. {
  335. Vector2I tangentCoords = GetTangentPosition(keyFrame, TangentType.In);
  336. canvas.DrawLine(keyframeCoords, tangentCoords, Color.LightGray);
  337. DrawDiamond(tangentCoords, 2, Color.Green, Color.Black);
  338. }
  339. if (IsTangentDisplayed(tangentMode, TangentType.Out))
  340. {
  341. Vector2I tangentCoords = GetTangentPosition(keyFrame, TangentType.Out);
  342. canvas.DrawLine(keyframeCoords, tangentCoords, Color.LightGray);
  343. DrawDiamond(tangentCoords, 2, Color.Green, Color.Black);
  344. }
  345. }
  346. /// <summary>
  347. /// Returns the position of the tangent, in element's pixel space.
  348. /// </summary>
  349. /// <param name="keyFrame">Keyframe that the tangent belongs to.</param>
  350. /// <param name="type">Which tangent to retrieve the position for.</param>
  351. /// <returns>Position of the tangent, relative to the this GUI element's origin, in pixels.</returns>
  352. private Vector2I GetTangentPosition(KeyFrame keyFrame, TangentType type)
  353. {
  354. Vector2I position = CurveToPixelSpace(new Vector2(keyFrame.time, keyFrame.value));
  355. Vector2 normal;
  356. if (type == TangentType.In)
  357. normal = -EdAnimationCurve.TangentToNormal(keyFrame.inTangent);
  358. else
  359. normal = EdAnimationCurve.TangentToNormal(keyFrame.outTangent);
  360. // X/Y ranges aren't scaled 1:1, adjust normal accordingly
  361. normal.x /= GetRange();
  362. normal.y /= yRange;
  363. normal = Vector2.Normalize(normal);
  364. // Convert normal (in percentage) to pixel values
  365. Vector2I offset = new Vector2I((int)(normal.x * TANGENT_LINE_DISTANCE),
  366. (int)(-normal.y * TANGENT_LINE_DISTANCE));
  367. return position + offset;
  368. }
  369. /// <summary>
  370. /// Checks if the tangent should be displayed, depending on the active tangent mode.
  371. /// </summary>
  372. /// <param name="mode">Tangent mode for the keyframe.</param>
  373. /// <param name="type">Which tangent to check for.</param>
  374. /// <returns>True if the tangent should be displayed.</returns>
  375. private bool IsTangentDisplayed(TangentMode mode, TangentType type)
  376. {
  377. if (mode == TangentMode.Auto)
  378. return false;
  379. else if (mode == TangentMode.Free)
  380. return true;
  381. if (type == TangentType.In)
  382. return mode.HasFlag(TangentMode.InFree);
  383. else
  384. return mode.HasFlag(TangentMode.OutFree);
  385. }
  386. /// <summary>
  387. /// Returns the range of times displayed by the timeline rounded to the multiple of FPS.
  388. /// </summary>
  389. /// <param name="padding">If true, extra range will be included to cover the right-most padding.</param>
  390. /// <returns>Time range rounded to a multiple of FPS.</returns>
  391. private float GetRange(bool padding = false)
  392. {
  393. float spf = 1.0f / fps;
  394. float range = xRange;
  395. if (padding)
  396. {
  397. float lengthPerPixel = xRange / drawableWidth;
  398. range += lengthPerPixel * GUIGraphTime.PADDING;
  399. }
  400. return ((int)range / spf) * spf;
  401. }
  402. /// <summary>
  403. /// Rebuilds the internal GUI elements. Should be called whenever timeline properties change.
  404. /// </summary>
  405. public void Rebuild()
  406. {
  407. canvas.Clear();
  408. if (curves == null)
  409. return;
  410. tickHandler.SetRange(0.0f, GetRange(true), drawableWidth + GUIGraphTime.PADDING);
  411. // Draw vertical frame markers
  412. int numTickLevels = tickHandler.NumLevels;
  413. for (int i = numTickLevels - 1; i >= 0; i--)
  414. {
  415. float[] ticks = tickHandler.GetTicks(i);
  416. float strength = tickHandler.GetLevelStrength(i);
  417. for (int j = 0; j < ticks.Length; j++)
  418. {
  419. Color color = COLOR_DARK_GRAY;
  420. color.a *= strength;
  421. DrawFrameMarker(ticks[j], color, false);
  422. }
  423. }
  424. // Draw center line
  425. DrawCenterLine();
  426. // Draw curves
  427. int curveIdx = 0;
  428. foreach (var curve in curves)
  429. {
  430. Color color = GetUniqueColor(curveIdx);
  431. DrawCurve(curve, color);
  432. // Draw keyframes
  433. KeyFrame[] keyframes = curve.KeyFrames;
  434. for (int i = 0; i < keyframes.Length; i++)
  435. {
  436. bool isSelected = IsSelected(curveIdx, i);
  437. DrawKeyframe(keyframes[i].time, keyframes[i].value, isSelected);
  438. if (isSelected)
  439. DrawTangents(keyframes[i], curve.TangentModes[i]);
  440. }
  441. curveIdx++;
  442. }
  443. // Draw selected frame marker
  444. if (markedFrameIdx != -1)
  445. DrawFrameMarker(GetTimeForFrame(markedFrameIdx), Color.BansheeOrange, true);
  446. }
  447. /// <summary>
  448. /// Generates a unique color based on the provided index.
  449. /// </summary>
  450. /// <param name="idx">Index to use for generating a color. Should be less than 30 in order to guarantee reasonably
  451. /// different colors.</param>
  452. /// <returns>Unique color.</returns>
  453. private Color GetUniqueColor(int idx)
  454. {
  455. const int COLOR_SPACING = 359 / 15;
  456. float hue = ((idx * COLOR_SPACING) % 359) / 359.0f;
  457. return Color.HSV2RGB(new Color(hue, 175.0f / 255.0f, 175.0f / 255.0f));
  458. }
  459. /// <summary>
  460. /// Checks is the provided key-frame currently marked as selected.
  461. /// </summary>
  462. /// <param name="curveIdx">Index of the curve the keyframe is on.</param>
  463. /// <param name="keyIdx">Index of the keyframe.</param>
  464. /// <returns>True if selected, false otherwise.</returns>
  465. private bool IsSelected(int curveIdx, int keyIdx)
  466. {
  467. if (selectedKeyframes == null)
  468. return false;
  469. if (curveIdx < 0 || curveIdx >= selectedKeyframes.Length)
  470. return false;
  471. if (keyIdx < 0 || keyIdx >= selectedKeyframes[curveIdx].Length)
  472. return false;
  473. return selectedKeyframes[curveIdx][keyIdx];
  474. }
  475. /// <summary>
  476. /// Draws the curve using the provided color.
  477. /// </summary>
  478. /// <param name="curve">Curve to draw within the currently set range. </param>
  479. /// <param name="color">Color to draw the curve with.</param>
  480. private void DrawCurve(EdAnimationCurve curve, Color color)
  481. {
  482. float range = GetRange();
  483. float lengthPerPixel = range / drawableWidth;
  484. float pixelsPerHeight = height/yRange;
  485. int heightOffset = height/2; // So that y = 0 is at center of canvas
  486. KeyFrame[] keyframes = curve.KeyFrames;
  487. if (keyframes.Length < 0)
  488. return;
  489. // Draw start line
  490. {
  491. float start = MathEx.Clamp(keyframes[0].time, 0.0f, range);
  492. int startPixel = (int)(start / lengthPerPixel);
  493. int xPosStart = 0;
  494. int xPosEnd = GUIGraphTime.PADDING + startPixel;
  495. int yPos = (int)(curve.Evaluate(0.0f, false) * pixelsPerHeight);
  496. yPos = heightOffset - yPos; // Offset and flip height (canvas Y goes down)
  497. Vector2I a = new Vector2I(xPosStart, yPos);
  498. Vector2I b = new Vector2I(xPosEnd, yPos);
  499. canvas.DrawLine(a, b, COLOR_MID_GRAY);
  500. }
  501. List<Vector2I> linePoints = new List<Vector2I>();
  502. // Draw in between keyframes
  503. for (int i = 0; i < keyframes.Length - 1; i++)
  504. {
  505. float start = MathEx.Clamp(keyframes[i].time, 0.0f, range);
  506. float end = MathEx.Clamp(keyframes[i + 1].time, 0.0f, range);
  507. int startPixel = (int)(start / lengthPerPixel);
  508. int endPixel = (int)(end / lengthPerPixel);
  509. bool isStep = keyframes[i].outTangent == float.PositiveInfinity ||
  510. keyframes[i + 1].inTangent == float.PositiveInfinity;
  511. // If step tangent, draw the required lines without sampling, as the sampling will miss the step
  512. if (isStep)
  513. {
  514. // Line from left to right frame
  515. int xPos = startPixel;
  516. int yPosStart = (int)(curve.Evaluate(start, false) * pixelsPerHeight);
  517. yPosStart = heightOffset - yPosStart; // Offset and flip height (canvas Y goes down)
  518. linePoints.Add(new Vector2I(GUIGraphTime.PADDING + xPos, yPosStart));
  519. xPos = endPixel;
  520. linePoints.Add(new Vector2I(GUIGraphTime.PADDING + xPos, yPosStart));
  521. // Line representing the step
  522. int yPosEnd = (int)(curve.Evaluate(end, false) * pixelsPerHeight);
  523. yPosEnd = heightOffset - yPosEnd; // Offset and flip height (canvas Y goes down)
  524. linePoints.Add(new Vector2I(GUIGraphTime.PADDING + xPos, yPosEnd));
  525. }
  526. else // Draw normally
  527. {
  528. int numSplits;
  529. float timeIncrement;
  530. if (startPixel != endPixel)
  531. {
  532. float fNumSplits = (endPixel - startPixel)/(float) LINE_SPLIT_WIDTH;
  533. numSplits = MathEx.FloorToInt(fNumSplits);
  534. float remainder = fNumSplits - numSplits;
  535. float lengthRounded = (end - start)*(numSplits/fNumSplits);
  536. timeIncrement = lengthRounded/numSplits;
  537. numSplits += MathEx.CeilToInt(remainder) + 1;
  538. }
  539. else
  540. {
  541. numSplits = 1;
  542. timeIncrement = 0.0f;
  543. }
  544. for (int j = 0; j < numSplits; j++)
  545. {
  546. int xPos = Math.Min(startPixel + j * LINE_SPLIT_WIDTH, endPixel);
  547. float t = Math.Min(start + j * timeIncrement, end);
  548. int yPos = (int)(curve.Evaluate(t, false) * pixelsPerHeight);
  549. yPos = heightOffset - yPos; // Offset and flip height (canvas Y goes down)
  550. linePoints.Add(new Vector2I(GUIGraphTime.PADDING + xPos, yPos));
  551. }
  552. }
  553. }
  554. canvas.DrawPolyLine(linePoints.ToArray(), color);
  555. // Draw end line
  556. {
  557. float end = MathEx.Clamp(keyframes[keyframes.Length - 1].time, 0.0f, range);
  558. int endPixel = (int)(end / lengthPerPixel);
  559. int xPosStart = GUIGraphTime.PADDING + endPixel;
  560. int xPosEnd = width;
  561. int yPos = (int)(curve.Evaluate(range, false) * pixelsPerHeight);
  562. yPos = heightOffset - yPos; // Offset and flip height (canvas Y goes down)
  563. Vector2I a = new Vector2I(xPosStart, yPos);
  564. Vector2I b = new Vector2I(xPosEnd, yPos);
  565. canvas.DrawLine(a, b, COLOR_MID_GRAY);
  566. }
  567. }
  568. }
  569. /** }@ */
  570. }