GUICurveDrawing.cs 25 KB

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