GUICurveDrawing.cs 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905
  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. private bool drawMarkers = true;
  27. private bool drawRange = false;
  28. /// <summary>
  29. /// Creates a new curve drawing GUI element.
  30. /// </summary>
  31. /// <param name="layout">Layout into which to add the GUI element.</param>
  32. /// <param name="width">Width of the element in pixels.</param>
  33. /// <param name="height">Height of the element in pixels.</param>
  34. /// <param name="curveInfos">Initial set of curves to display. </param>
  35. /// <param name="drawMarkers">If enabled draw frame markers, or if disabled draw just the curve.</param>
  36. public GUICurveDrawing(GUILayout layout, int width, int height, CurveDrawInfo[] curveInfos, bool drawMarkers = true)
  37. :base(layout, width, height)
  38. {
  39. if(drawMarkers)
  40. tickHandler = new GUIGraphTicks(GUITickStepType.Time);
  41. this.curveInfos = curveInfos;
  42. this.drawMarkers = drawMarkers;
  43. ClearSelectedKeyframes(); // Makes sure the array is initialized
  44. }
  45. /// <summary>
  46. /// Change the set of curves to display.
  47. /// </summary>
  48. /// <param name="curveInfos">New set of curves to draw on the GUI element.</param>
  49. public void SetCurves(CurveDrawInfo[] curveInfos)
  50. {
  51. this.curveInfos = curveInfos;
  52. }
  53. /// <summary>
  54. /// Changes the visible range that the GUI element displays.
  55. /// </summary>
  56. /// <param name="xRange">Range of the horizontal area. Displayed area will range from [0, xRange].</param>
  57. /// <param name="yRange">Range of the vertical area. Displayed area will range from
  58. /// [-yRange * 0.5, yRange * 0.5]</param>
  59. public void SetRange(float xRange, float yRange)
  60. {
  61. SetRange(xRange);
  62. this.yRange = yRange;
  63. }
  64. /// <summary>
  65. /// Returns the offset at which the displayed timeline values start at.
  66. /// </summary>
  67. /// <param name="offset">Value to start the timeline values at.</param>
  68. public void SetOffset(Vector2 offset)
  69. {
  70. SetOffset(offset.x);
  71. yOffset = offset.y;
  72. }
  73. /// <summary>
  74. /// Changes curve rendering mode. Normally the curves are drawn individually, but when range rendering is enabled
  75. /// the area between the first two curves is drawn instead. This setting is ignored if less than two curves are
  76. /// present. More than two curves are also ignored.
  77. /// </summary>
  78. /// <param name="drawRange">True to enable range rendering mode, false to enable individual curve rendering.</param>
  79. public void SetDrawRange(bool drawRange)
  80. {
  81. this.drawRange = drawRange;
  82. }
  83. /// <summary>
  84. /// Marks the specified key-frame as selected, changing the way it is displayed.
  85. /// </summary>
  86. /// <param name="keyframeRef">Keyframe reference containing the curve and keyframe index.</param>
  87. /// <param name="selected">True to select it, false to deselect it.</param>
  88. public void SelectKeyframe(KeyframeRef keyframeRef, bool selected)
  89. {
  90. if (selectedKeyframes == null)
  91. return;
  92. if (keyframeRef.curveIdx < 0 || keyframeRef.curveIdx >= selectedKeyframes.Length)
  93. return;
  94. if (keyframeRef.keyIdx < 0 || keyframeRef.keyIdx >= selectedKeyframes[keyframeRef.curveIdx].Length)
  95. return;
  96. selectedKeyframes[keyframeRef.curveIdx][keyframeRef.keyIdx] = selected;
  97. }
  98. /// <summary>
  99. /// Clears any key-frames that were marked as selected.
  100. /// </summary>
  101. public void ClearSelectedKeyframes()
  102. {
  103. selectedKeyframes = new bool[curveInfos.Length][];
  104. for (int i = 0; i < curveInfos.Length; i++)
  105. {
  106. KeyFrame[] keyframes = curveInfos[i].curve.KeyFrames;
  107. selectedKeyframes[i] = new bool[keyframes.Length];
  108. }
  109. }
  110. /// <summary>
  111. /// Attempts to find a curve under the provided coordinates.
  112. /// </summary>
  113. /// <param name="pixelCoords">Coordinates relative to this GUI element in pixels.</param>
  114. /// <returns>Index of the curve, or -1 if none found.</returns>
  115. public int FindCurve(Vector2I pixelCoords)
  116. {
  117. PixelToCurveSpace(pixelCoords, out var curveCoords, true);
  118. float time = curveCoords.x;
  119. float nearestDistance = float.MaxValue;
  120. int curveIdx = -1;
  121. for (int i = 0; i < curveInfos.Length; i++)
  122. {
  123. EdAnimationCurve curve = curveInfos[i].curve;
  124. float value = curve.Evaluate(time, false);
  125. Vector2I curPixelPos = CurveToPixelSpace(new Vector2(time, value));
  126. float distanceToKey = Vector2I.Distance(pixelCoords, curPixelPos);
  127. if (distanceToKey < nearestDistance)
  128. {
  129. nearestDistance = distanceToKey;
  130. curveIdx = i;
  131. }
  132. }
  133. // We're not near any curve
  134. if (nearestDistance > 5.0f)
  135. return -1;
  136. return curveIdx;
  137. }
  138. /// <summary>
  139. /// Attempts to find a keyframe under the provided coordinates.
  140. /// </summary>
  141. /// <param name="pixelCoords">Coordinates relative to this GUI element in pixels.</param>
  142. /// <param name="keyframe">Output object containing keyframe index and index of the curve it belongs to. Only valid
  143. /// if method returns true.</param>
  144. /// <returns>True if there is a keyframe under the coordinates, false otherwise.</returns>
  145. public bool FindKeyFrame(Vector2I pixelCoords, out KeyframeRef keyframe)
  146. {
  147. keyframe = new KeyframeRef();
  148. float nearestDistance = float.MaxValue;
  149. for (int i = 0; i < curveInfos.Length; i++)
  150. {
  151. EdAnimationCurve curve = curveInfos[i].curve;
  152. KeyFrame[] keyframes = curve.KeyFrames;
  153. for (int j = 0; j < keyframes.Length; j++)
  154. {
  155. Vector2 keyframeCurveCoords = new Vector2(keyframes[j].time, keyframes[j].value);
  156. Vector2I keyframeCoords = CurveToPixelSpace(keyframeCurveCoords);
  157. float distanceToKey = Vector2I.Distance(pixelCoords, keyframeCoords);
  158. if (distanceToKey < nearestDistance)
  159. {
  160. nearestDistance = distanceToKey;
  161. keyframe.keyIdx = j;
  162. keyframe.curveIdx = i;
  163. }
  164. }
  165. }
  166. // We're not near any keyframe
  167. if (nearestDistance > 5.0f)
  168. return false;
  169. return true;
  170. }
  171. /// <summary>
  172. /// Attempts to find a a tangent handle under the provided coordinates.
  173. /// </summary>
  174. /// <param name="pixelCoords">Coordinates relative to this GUI element in pixels.</param>
  175. /// <param name="tangent">Output object containing keyframe information and tangent type. Only valid if method
  176. /// returns true.</param>
  177. /// <returns>True if there is a tangent handle under the coordinates, false otherwise.</returns>
  178. public bool FindTangent(Vector2I pixelCoords, out TangentRef tangent)
  179. {
  180. tangent = new TangentRef();
  181. float nearestDistance = float.MaxValue;
  182. for (int i = 0; i < curveInfos.Length; i++)
  183. {
  184. EdAnimationCurve curve = curveInfos[i].curve;
  185. KeyFrame[] keyframes = curve.KeyFrames;
  186. for (int j = 0; j < keyframes.Length; j++)
  187. {
  188. if (!IsSelected(i, j))
  189. continue;
  190. TangentMode tangentMode = curve.TangentModes[j];
  191. if (IsTangentDisplayed(tangentMode, TangentType.In))
  192. {
  193. Vector2I tangentCoords = GetTangentPosition(keyframes[j], TangentType.In);
  194. float distanceToHandle = Vector2I.Distance(pixelCoords, tangentCoords);
  195. if (distanceToHandle < nearestDistance)
  196. {
  197. nearestDistance = distanceToHandle;
  198. tangent.keyframeRef.keyIdx = j;
  199. tangent.keyframeRef.curveIdx = i;
  200. tangent.type = TangentType.In;
  201. }
  202. ; }
  203. if (IsTangentDisplayed(tangentMode, TangentType.Out))
  204. {
  205. Vector2I tangentCoords = GetTangentPosition(keyframes[j], TangentType.Out);
  206. float distanceToHandle = Vector2I.Distance(pixelCoords, tangentCoords);
  207. if (distanceToHandle < nearestDistance)
  208. {
  209. nearestDistance = distanceToHandle;
  210. tangent.keyframeRef.keyIdx = j;
  211. tangent.keyframeRef.curveIdx = i;
  212. tangent.type = TangentType.Out;
  213. }
  214. }
  215. }
  216. }
  217. // We're not near any keyframe
  218. if (nearestDistance > 5.0f)
  219. return false;
  220. return true;
  221. }
  222. /// <summary>
  223. /// Converts pixel coordinates into coordinates in curve space.
  224. /// </summary>
  225. /// <param name="pixelCoords">Coordinates relative to this GUI element, in pixels.</param>
  226. /// <param name="curveCoords">Curve coordinates within the range as specified by <see cref="SetRange"/>. Only
  227. /// valid when function returns true.</param>
  228. /// <param name="padding">Determines should coordinates over padding be registered.</param>
  229. /// <returns>True if the coordinates are within the curve area, false otherwise.</returns>
  230. public bool PixelToCurveSpace(Vector2I pixelCoords, out Vector2 curveCoords, bool padding = false)
  231. {
  232. Rect2I bounds = canvas.Bounds;
  233. bool outsideHorizontal;
  234. if (padding)
  235. outsideHorizontal = pixelCoords.x < bounds.x || pixelCoords.x >= (bounds.x + bounds.width);
  236. else
  237. outsideHorizontal = pixelCoords.x < (bounds.x + PADDING) || pixelCoords.x >= (bounds.x + bounds.width - PADDING);
  238. // Check if outside of curve drawing bounds
  239. if (outsideHorizontal || pixelCoords.y < bounds.y || pixelCoords.y >= (bounds.y + bounds.height))
  240. {
  241. curveCoords = new Vector2();
  242. return false;
  243. }
  244. // Find time and value of the place under the coordinates
  245. Vector2I relativeCoords = pixelCoords - new Vector2I(bounds.x + PADDING, bounds.y);
  246. float lengthPerPixel = GetRange() / drawableWidth;
  247. float heightPerPixel = yRange / height;
  248. float centerOffset = yRange / 2.0f;
  249. float t = rangeOffset + relativeCoords.x * lengthPerPixel;
  250. float value = yOffset + centerOffset - relativeCoords.y * heightPerPixel;
  251. curveCoords = new Vector2();
  252. curveCoords.x = t;
  253. curveCoords.y = value;
  254. return true;
  255. }
  256. /// <summary>
  257. /// Converts coordinate in curve space (time, value) into pixel coordinates relative to this element's origin.
  258. /// </summary>
  259. /// <param name="curveCoords">Time and value of the location to convert.</param>
  260. /// <returns>Coordinates relative to this element's origin, in pixels.</returns>
  261. public Vector2I CurveToPixelSpace(Vector2 curveCoords)
  262. {
  263. int heightOffset = height / 2; // So that y = 0 is at center of canvas
  264. Vector2 relativeCurveCoords = curveCoords - new Vector2(rangeOffset, yOffset);
  265. Vector2I pixelCoords = new Vector2I();
  266. pixelCoords.x = (int)((relativeCurveCoords.x / GetRange()) * drawableWidth) + PADDING;
  267. pixelCoords.y = heightOffset - (int)((relativeCurveCoords.y / yRange) * height);
  268. return pixelCoords;
  269. }
  270. /// <summary>
  271. /// Generates a unique color based on the provided index.
  272. /// </summary>
  273. /// <param name="idx">Index to use for generating a color. Should be less than 30 in order to guarantee reasonably
  274. /// different colors.</param>
  275. /// <returns>Unique color.</returns>
  276. public static Color GetUniqueColor(int idx)
  277. {
  278. const int COLOR_SPACING = 359 / 15;
  279. float hue = ((idx * COLOR_SPACING) % 359) / 359.0f;
  280. return Color.HSV2RGB(new Color(hue, 175.0f / 255.0f, 175.0f / 255.0f));
  281. }
  282. /// <summary>
  283. /// Draws a vertical frame marker on the curve area.
  284. /// </summary>
  285. /// <param name="t">Time at which to draw the marker.</param>
  286. /// <param name="color">Color with which to draw the marker.</param>
  287. /// <param name="onTop">Determines should the marker be drawn above or below the curve.</param>
  288. private void DrawFrameMarker(float t, Color color, bool onTop)
  289. {
  290. int xPos = (int)(((t - rangeOffset) / GetRange()) * drawableWidth) + PADDING;
  291. Vector2I start = new Vector2I(xPos, 0);
  292. Vector2I end = new Vector2I(xPos, height);
  293. byte depth;
  294. if (onTop)
  295. depth = 110;
  296. else
  297. depth = 130;
  298. canvas.DrawLine(start, end, color, depth);
  299. }
  300. /// <summary>
  301. /// Draws a horizontal line representing the line at y = 0.
  302. /// </summary>
  303. private void DrawCenterLine()
  304. {
  305. Vector2I center = CurveToPixelSpace(new Vector2(0.0f, 0.0f));
  306. Vector2I start = new Vector2I(0, center.y);
  307. Vector2I end = new Vector2I(width, center.y);
  308. canvas.DrawLine(start, end, COLOR_DARK_GRAY, 130);
  309. }
  310. /// <summary>
  311. /// Draws a diamond shape of the specified size at the coordinates.
  312. /// </summary>
  313. /// <param name="center">Position at which to place the diamond's center, in pixel coordinates.</param>
  314. /// <param name="size">Determines number of pixels to extend the diamond in each direction.</param>
  315. /// <param name="innerColor">Color of the diamond's background.</param>
  316. /// <param name="outerColor">Color of the diamond's outline.</param>
  317. private void DrawDiamond(Vector2I center, int size, Color innerColor, Color outerColor)
  318. {
  319. Vector2I a = new Vector2I(center.x - size, center.y);
  320. Vector2I b = new Vector2I(center.x, center.y - size);
  321. Vector2I c = new Vector2I(center.x + size, center.y);
  322. Vector2I d = new Vector2I(center.x, center.y + size);
  323. // Draw diamond shape
  324. Vector2I[] linePoints = new Vector2I[] { a, b, c, d, a };
  325. Vector2I[] trianglePoints = new Vector2I[] { b, c, a, d };
  326. canvas.DrawTriangleStrip(trianglePoints, innerColor, 101);
  327. canvas.DrawPolyLine(linePoints, outerColor, 100);
  328. }
  329. /// <summary>
  330. /// Draws a keyframe a the specified time and value.
  331. /// </summary>
  332. /// <param name="t">Time to draw the keyframe at.</param>
  333. /// <param name="y">Y value to draw the keyframe at.</param>
  334. /// <param name="selected">Determines should the keyframe be drawing using the selected color scheme, or normally.
  335. /// </param>
  336. private void DrawKeyframe(float t, float y, bool selected)
  337. {
  338. Vector2I pixelCoords = CurveToPixelSpace(new Vector2(t, y));
  339. if (selected)
  340. DrawDiamond(pixelCoords, 3, Color.White, Color.BansheeOrange);
  341. else
  342. DrawDiamond(pixelCoords, 3, Color.White, Color.Black);
  343. }
  344. /// <summary>
  345. /// Draws zero, one or two tangents for the specified keyframe. Whether tangents are drawn depends on the provided
  346. /// mode.
  347. /// </summary>
  348. /// <param name="keyFrame">Keyframe to draw the tangents for.</param>
  349. /// <param name="tangentMode">Type of tangents in the keyframe.</param>
  350. private void DrawTangents(KeyFrame keyFrame, TangentMode tangentMode)
  351. {
  352. Vector2I keyframeCoords = CurveToPixelSpace(new Vector2(keyFrame.time, keyFrame.value));
  353. if (IsTangentDisplayed(tangentMode, TangentType.In))
  354. {
  355. Vector2I tangentCoords = GetTangentPosition(keyFrame, TangentType.In);
  356. canvas.DrawLine(keyframeCoords, tangentCoords, Color.LightGray);
  357. DrawDiamond(tangentCoords, 2, Color.Green, Color.Black);
  358. }
  359. if (IsTangentDisplayed(tangentMode, TangentType.Out))
  360. {
  361. Vector2I tangentCoords = GetTangentPosition(keyFrame, TangentType.Out);
  362. canvas.DrawLine(keyframeCoords, tangentCoords, Color.LightGray);
  363. DrawDiamond(tangentCoords, 2, Color.Green, Color.Black);
  364. }
  365. }
  366. /// <summary>
  367. /// Returns the position of the tangent, in element's pixel space.
  368. /// </summary>
  369. /// <param name="keyFrame">Keyframe that the tangent belongs to.</param>
  370. /// <param name="type">Which tangent to retrieve the position for.</param>
  371. /// <returns>Position of the tangent, relative to the this GUI element's origin, in pixels.</returns>
  372. private Vector2I GetTangentPosition(KeyFrame keyFrame, TangentType type)
  373. {
  374. Vector2I position = CurveToPixelSpace(new Vector2(keyFrame.time, keyFrame.value));
  375. Vector2 normal;
  376. if (type == TangentType.In)
  377. normal = -EdAnimationCurve.TangentToNormal(keyFrame.inTangent);
  378. else
  379. normal = EdAnimationCurve.TangentToNormal(keyFrame.outTangent);
  380. // X/Y ranges aren't scaled 1:1, adjust normal accordingly
  381. normal.x /= GetRange();
  382. normal.y /= yRange;
  383. normal = Vector2.Normalize(normal);
  384. // Convert normal (in percentage) to pixel values
  385. Vector2I offset = new Vector2I((int)(normal.x * TANGENT_LINE_DISTANCE),
  386. (int)(-normal.y * TANGENT_LINE_DISTANCE));
  387. return position + offset;
  388. }
  389. /// <summary>
  390. /// Checks if the tangent should be displayed, depending on the active tangent mode.
  391. /// </summary>
  392. /// <param name="mode">Tangent mode for the keyframe.</param>
  393. /// <param name="type">Which tangent to check for.</param>
  394. /// <returns>True if the tangent should be displayed.</returns>
  395. private bool IsTangentDisplayed(TangentMode mode, TangentType type)
  396. {
  397. if (mode == TangentMode.Auto)
  398. return false;
  399. else if (mode == TangentMode.Free)
  400. return true;
  401. if (type == TangentType.In)
  402. return mode.HasFlag(TangentMode.InFree);
  403. else
  404. return mode.HasFlag(TangentMode.OutFree);
  405. }
  406. /// <summary>
  407. /// Rebuilds the internal GUI elements. Should be called whenever timeline properties change.
  408. /// </summary>
  409. public override void Rebuild()
  410. {
  411. canvas.Clear();
  412. if (curveInfos == null)
  413. return;
  414. if (drawMarkers)
  415. {
  416. tickHandler.SetRange(rangeOffset, rangeOffset + GetRange(true), drawableWidth + PADDING);
  417. // Draw vertical frame markers
  418. int numTickLevels = tickHandler.NumLevels;
  419. for (int i = numTickLevels - 1; i >= 0; i--)
  420. {
  421. float[] ticks = tickHandler.GetTicks(i);
  422. float strength = tickHandler.GetLevelStrength(i);
  423. for (int j = 0; j < ticks.Length; j++)
  424. {
  425. Color color = COLOR_DARK_GRAY;
  426. color.a *= strength;
  427. DrawFrameMarker(ticks[j], color, false);
  428. }
  429. }
  430. // Draw center line
  431. DrawCenterLine();
  432. }
  433. // Draw range
  434. int curvesToDraw = curveInfos.Length;
  435. if (drawRange && curveInfos.Length >= 2)
  436. {
  437. EdAnimationCurve[] curves = {curveInfos[0].curve, curveInfos[1].curve};
  438. DrawCurveRange(curves, new Color(1.0f, 0.0f, 0.0f, 0.3f));
  439. curvesToDraw = 2;
  440. }
  441. // Draw curves
  442. for (int i = 0; i < curvesToDraw; i++)
  443. {
  444. DrawCurve(curveInfos[i].curve, curveInfos[i].color);
  445. // Draw keyframes
  446. KeyFrame[] keyframes = curveInfos[i].curve.KeyFrames;
  447. for (int j = 0; j < keyframes.Length; j++)
  448. {
  449. bool isSelected = IsSelected(i, j);
  450. DrawKeyframe(keyframes[j].time, keyframes[j].value, isSelected);
  451. if (isSelected)
  452. DrawTangents(keyframes[j], curveInfos[i].curve.TangentModes[j]);
  453. }
  454. }
  455. // Draw selected frame marker
  456. if (drawMarkers && markedFrameIdx != -1)
  457. DrawFrameMarker(GetTimeForFrame(markedFrameIdx), Color.BansheeOrange, true);
  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(true);
  483. float lengthPerPixel = range / drawableWidth;
  484. KeyFrame[] keyframes = curve.KeyFrames;
  485. if (keyframes.Length <= 0)
  486. return;
  487. // Draw start line
  488. {
  489. float curveStart = keyframes[0].time;
  490. float curveValue = curve.Evaluate(curveStart, false);
  491. Vector2I end = CurveToPixelSpace(new Vector2(curveStart, curveValue));
  492. Vector2I start = new Vector2I(-GUIGraphTime.PADDING, end.y);
  493. if(start.x < end.x)
  494. canvas.DrawLine(start, end, COLOR_MID_GRAY);
  495. }
  496. List<Vector2I> linePoints = new List<Vector2I>();
  497. // Draw in between keyframes
  498. float startVisibleTime = rangeOffset;
  499. float endVisibleTime = startVisibleTime + range;
  500. for (int i = 0; i < keyframes.Length - 1; i++)
  501. {
  502. float start = keyframes[i].time;
  503. float end = keyframes[i + 1].time;
  504. if (end < startVisibleTime || start > endVisibleTime)
  505. continue;
  506. bool isStep = keyframes[i].outTangent == float.PositiveInfinity ||
  507. keyframes[i + 1].inTangent == float.PositiveInfinity;
  508. // If step tangent, draw the required lines without sampling, as the sampling will miss the step
  509. if (isStep)
  510. {
  511. float startValue = curve.Evaluate(start, false);
  512. float endValue = curve.Evaluate(end, false);
  513. linePoints.Add(CurveToPixelSpace(new Vector2(start, startValue)));
  514. linePoints.Add(CurveToPixelSpace(new Vector2(end, startValue)));
  515. linePoints.Add(CurveToPixelSpace(new Vector2(end, endValue)));
  516. }
  517. else // Draw normally
  518. {
  519. float splitIncrement = LINE_SPLIT_WIDTH*lengthPerPixel;
  520. float startValue = keyframes[i].value;
  521. float endValue = keyframes[i + 1].value;
  522. Vector2I startPixel = new Vector2I();
  523. startPixel.x = (int) (start / lengthPerPixel);
  524. startPixel.y = (int) (startValue / lengthPerPixel);
  525. Vector2I endPixel = new Vector2I();
  526. endPixel.x = (int) (end / lengthPerPixel);
  527. endPixel.y = (int) (endValue / lengthPerPixel);
  528. int distance = Vector2I.Distance(startPixel, endPixel);
  529. int numSplits;
  530. if (distance > 0)
  531. {
  532. float fNumSplits = distance / splitIncrement;
  533. numSplits = MathEx.CeilToInt(fNumSplits);
  534. splitIncrement = distance/(float)numSplits;
  535. }
  536. else
  537. {
  538. numSplits = 1;
  539. splitIncrement = 0.0f;
  540. }
  541. for (int j = 0; j < numSplits; j++)
  542. {
  543. float t = Math.Min(start + j * splitIncrement, end);
  544. float value = curve.Evaluate(t, false);
  545. linePoints.Add(CurveToPixelSpace(new Vector2(t, value)));
  546. }
  547. }
  548. }
  549. canvas.DrawPolyLine(linePoints.ToArray(), color);
  550. // Draw end line
  551. {
  552. float curveEnd = keyframes[keyframes.Length - 1].time;
  553. float curveValue = curve.Evaluate(curveEnd, false);
  554. Vector2I start = CurveToPixelSpace(new Vector2(curveEnd, curveValue));
  555. Vector2I end = new Vector2I(width, start.y);
  556. if(start.x < end.x)
  557. canvas.DrawLine(start, end, COLOR_MID_GRAY);
  558. }
  559. }
  560. /// <summary>
  561. /// Draws the area between two curves using the provided color.
  562. /// </summary>
  563. /// <param name="curves">Curves to draw within the currently set range.</param>
  564. /// <param name="color">Color to draw the area with.</param>
  565. private void DrawCurveRange(EdAnimationCurve[] curves, Color color)
  566. {
  567. float range = GetRange(true);
  568. if(curves.Length != 2 || curves[0] == null || curves[1] == null)
  569. return;
  570. KeyFrame[][] keyframes = { curves[0].KeyFrames, curves[1].KeyFrames };
  571. if (keyframes[0].Length <= 0 || keyframes[1].Length <= 0)
  572. return;
  573. int numSamples = (drawableWidth + LINE_SPLIT_WIDTH - 1) / LINE_SPLIT_WIDTH;
  574. float timePerSample = range / numSamples;
  575. float time = rangeOffset;
  576. float lengthPerPixel = rangeLength / drawableWidth;
  577. time -= lengthPerPixel * PADDING;
  578. int[] keyframeIndices = {0, 0};
  579. // Find first valid keyframe indices
  580. for (int curveIdx = 0; curveIdx < 2; curveIdx++)
  581. {
  582. keyframeIndices[curveIdx] = keyframes[curveIdx].Length;
  583. for (int i = 0; i < keyframes[curveIdx].Length; i++)
  584. {
  585. if (keyframes[curveIdx][i].time > time)
  586. keyframeIndices[curveIdx] = i;
  587. }
  588. }
  589. List<float> times = new List<float>();
  590. List<float>[] points = { new List<float>(), new List<float>() };
  591. // Determine start points
  592. for (int curveIdx = 0; curveIdx < 2; curveIdx++)
  593. {
  594. float value = curves[curveIdx].Evaluate(time, false);
  595. points[curveIdx].Add(value);
  596. }
  597. times.Add(time);
  598. float rangeEnd = rangeOffset + range;
  599. while (time < rangeEnd)
  600. {
  601. float nextTime = time + timePerSample;
  602. bool hasStep = false;
  603. // Determine time to sample at. Use fixed increments unless there's a step keyframe within our increment in
  604. // which case we use its time so we can evaluate it directly
  605. for (int curveIdx = 0; curveIdx < 2; curveIdx++)
  606. {
  607. int keyframeIdx = keyframeIndices[curveIdx];
  608. if (keyframeIdx < keyframes[curveIdx].Length)
  609. {
  610. KeyFrame keyframe = keyframes[curveIdx][keyframeIdx];
  611. bool isStep = keyframe.inTangent == float.PositiveInfinity ||
  612. keyframe.outTangent == float.PositiveInfinity;
  613. if (isStep && keyframe.time <= nextTime)
  614. {
  615. nextTime = Math.Min(nextTime, keyframe.time);
  616. hasStep = true;
  617. }
  618. }
  619. }
  620. // Evaluate
  621. if (hasStep)
  622. {
  623. for (int curveIdx = 0; curveIdx < 2; curveIdx++)
  624. {
  625. int keyframeIdx = keyframeIndices[curveIdx];
  626. if (keyframeIdx < keyframes[curveIdx].Length)
  627. {
  628. KeyFrame keyframe = keyframes[curveIdx][keyframeIdx];
  629. if (MathEx.ApproxEquals(keyframe.time, nextTime))
  630. {
  631. if (keyframeIdx > 0)
  632. {
  633. KeyFrame prevKeyframe = keyframes[curveIdx][keyframeIdx - 1];
  634. points[curveIdx].Add(prevKeyframe.value);
  635. }
  636. else
  637. points[curveIdx].Add(keyframe.value);
  638. points[curveIdx].Add(keyframe.value);
  639. }
  640. else
  641. {
  642. // The other curve has step but this one doesn't, we just insert the same value twice
  643. float value = curves[curveIdx].Evaluate(nextTime, false);
  644. points[curveIdx].Add(value);
  645. points[curveIdx].Add(value);
  646. }
  647. times.Add(nextTime);
  648. times.Add(nextTime);
  649. }
  650. }
  651. }
  652. else
  653. {
  654. for (int curveIdx = 0; curveIdx < 2; curveIdx++)
  655. points[curveIdx].Add(curves[curveIdx].Evaluate(nextTime, false));
  656. times.Add(nextTime);
  657. }
  658. // Advance keyframe indices
  659. for (int curveIdx = 0; curveIdx < 2; curveIdx++)
  660. {
  661. int keyframeIdx = keyframeIndices[curveIdx];
  662. while (keyframeIdx < keyframes[curveIdx].Length)
  663. {
  664. KeyFrame keyframe = keyframes[curveIdx][keyframeIdx];
  665. if (keyframe.time > nextTime)
  666. break;
  667. keyframeIdx = ++keyframeIndices[curveIdx];
  668. }
  669. }
  670. time = nextTime;
  671. }
  672. // End points
  673. for (int curveIdx = 0; curveIdx < 2; curveIdx++)
  674. {
  675. float value = curves[curveIdx].Evaluate(rangeEnd, false);
  676. points[curveIdx].Add(value);
  677. }
  678. times.Add(rangeEnd);
  679. int numQuads = times.Count - 1;
  680. List<Vector2I> vertices = new List<Vector2I>();
  681. for (int i = 0; i < numQuads; i++)
  682. {
  683. int idxLeft = points[0][i] < points[1][i] ? 0 : 1;
  684. int idxRight = points[0][i + 1] < points[1][i + 1] ? 0 : 1;
  685. Vector2[] left =
  686. {
  687. new Vector2(times[i], points[0][i]),
  688. new Vector2(times[i], points[1][i])
  689. };
  690. Vector2[] right =
  691. {
  692. new Vector2(times[i + 1], points[0][i + 1]),
  693. new Vector2(times[i + 1], points[1][i + 1])
  694. };
  695. if (idxLeft == idxRight)
  696. {
  697. int idxA = idxLeft;
  698. int idxB = (idxLeft + 1) % 2;
  699. vertices.Add(CurveToPixelSpace(left[idxB]));
  700. vertices.Add(CurveToPixelSpace(right[idxB]));
  701. vertices.Add(CurveToPixelSpace(left[idxA]));
  702. vertices.Add(CurveToPixelSpace(right[idxB]));
  703. vertices.Add(CurveToPixelSpace(right[idxA]));
  704. vertices.Add(CurveToPixelSpace(left[idxA]));
  705. }
  706. // Lines intersects, can't represent them with a single quad
  707. else if (idxLeft != idxRight)
  708. {
  709. int idxA = idxLeft;
  710. int idxB = (idxLeft + 1) % 2;
  711. Line2 lineA = new Line2(left[idxB], right[idxA] - left[idxB]);
  712. Line2 lineB = new Line2(left[idxA], right[idxB] - left[idxA]);
  713. if (lineA.Intersects(lineB, out var t))
  714. {
  715. Vector2 intersection = left[idxB] + t * (right[idxA] - left[idxB]);
  716. vertices.Add(CurveToPixelSpace(left[idxB]));
  717. vertices.Add(CurveToPixelSpace(intersection));
  718. vertices.Add(CurveToPixelSpace(left[idxA]));
  719. vertices.Add(CurveToPixelSpace(intersection));
  720. vertices.Add(CurveToPixelSpace(right[idxB]));
  721. vertices.Add(CurveToPixelSpace(right[idxA]));
  722. }
  723. }
  724. }
  725. canvas.DrawTriangleList(vertices.ToArray(), color, 129);
  726. }
  727. }
  728. /// <summary>
  729. /// Information necessary to draw a curve.
  730. /// </summary>
  731. internal struct CurveDrawInfo
  732. {
  733. public CurveDrawInfo(EdAnimationCurve curve, Color color)
  734. {
  735. this.curve = curve;
  736. this.color = color;
  737. }
  738. public EdAnimationCurve curve;
  739. public Color color;
  740. }
  741. /** }@ */
  742. }