GUICurveDrawing.cs 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897
  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();
  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 = MathEx.Clamp(keyframes[0].time, 0.0f, range);
  490. float curveValue = curve.Evaluate(0.0f, false);
  491. Vector2I start = CurveToPixelSpace(new Vector2(0.0f, curveValue));
  492. start.x -= GUIGraphTime.PADDING;
  493. Vector2I end = CurveToPixelSpace(new Vector2(curveStart, curveValue));
  494. canvas.DrawLine(start, end, COLOR_MID_GRAY);
  495. }
  496. List<Vector2I> linePoints = new List<Vector2I>();
  497. // Draw in between keyframes
  498. for (int i = 0; i < keyframes.Length - 1; i++)
  499. {
  500. float start = MathEx.Clamp(keyframes[i].time, 0.0f, range);
  501. float end = MathEx.Clamp(keyframes[i + 1].time, 0.0f, range);
  502. bool isStep = keyframes[i].outTangent == float.PositiveInfinity ||
  503. keyframes[i + 1].inTangent == float.PositiveInfinity;
  504. // If step tangent, draw the required lines without sampling, as the sampling will miss the step
  505. if (isStep)
  506. {
  507. float startValue = curve.Evaluate(start, false);
  508. float endValue = curve.Evaluate(end, false);
  509. linePoints.Add(CurveToPixelSpace(new Vector2(start, startValue)));
  510. linePoints.Add(CurveToPixelSpace(new Vector2(end, startValue)));
  511. linePoints.Add(CurveToPixelSpace(new Vector2(end, endValue)));
  512. }
  513. else // Draw normally
  514. {
  515. float splitIncrement = LINE_SPLIT_WIDTH*lengthPerPixel;
  516. float startValue = keyframes[i].value;
  517. float endValue = keyframes[i + 1].value;
  518. Vector2I startPixel = new Vector2I();
  519. startPixel.x = (int) (start / lengthPerPixel);
  520. startPixel.y = (int) (startValue / lengthPerPixel);
  521. Vector2I endPixel = new Vector2I();
  522. endPixel.x = (int) (end / lengthPerPixel);
  523. endPixel.y = (int) (endValue / lengthPerPixel);
  524. int distance = Vector2I.Distance(startPixel, endPixel);
  525. int numSplits;
  526. if (distance > 0)
  527. {
  528. float fNumSplits = distance / splitIncrement;
  529. numSplits = MathEx.CeilToInt(fNumSplits);
  530. splitIncrement = distance/(float)numSplits;
  531. }
  532. else
  533. {
  534. numSplits = 1;
  535. splitIncrement = 0.0f;
  536. }
  537. for (int j = 0; j < numSplits; j++)
  538. {
  539. float t = Math.Min(start + j * splitIncrement, end);
  540. float value = curve.Evaluate(t, false);
  541. linePoints.Add(CurveToPixelSpace(new Vector2(t, value)));
  542. }
  543. }
  544. }
  545. canvas.DrawPolyLine(linePoints.ToArray(), color);
  546. // Draw end line
  547. {
  548. float curveEnd = MathEx.Clamp(keyframes[keyframes.Length - 1].time, 0.0f, range);
  549. float curveValue = curve.Evaluate(range, false);
  550. Vector2I start = CurveToPixelSpace(new Vector2(curveEnd, curveValue));
  551. Vector2I end = new Vector2I(width, start.y);
  552. canvas.DrawLine(start, end, COLOR_MID_GRAY);
  553. }
  554. }
  555. /// <summary>
  556. /// Draws the area between two curves using the provided color.
  557. /// </summary>
  558. /// <param name="curves">Curves to draw within the currently set range.</param>
  559. /// <param name="color">Color to draw the area with.</param>
  560. private void DrawCurveRange(EdAnimationCurve[] curves, Color color)
  561. {
  562. float range = GetRange();
  563. if(curves.Length != 2 || curves[0] == null || curves[1] == null)
  564. return;
  565. KeyFrame[][] keyframes = { curves[0].KeyFrames, curves[1].KeyFrames };
  566. if (keyframes[0].Length <= 0 || keyframes[1].Length <= 0)
  567. return;
  568. int numSamples = (drawableWidth + LINE_SPLIT_WIDTH - 1) / LINE_SPLIT_WIDTH;
  569. float timePerSample = range / numSamples;
  570. float time = rangeOffset;
  571. int[] keyframeIndices = {0, 0};
  572. // Find first valid keyframe indices
  573. for (int curveIdx = 0; curveIdx < 2; curveIdx++)
  574. {
  575. keyframeIndices[curveIdx] = keyframes[curveIdx].Length;
  576. for (int i = 0; i < keyframes[curveIdx].Length; i++)
  577. {
  578. if (keyframes[curveIdx][i].time > time)
  579. keyframeIndices[curveIdx] = i;
  580. }
  581. }
  582. List<float> times = new List<float>();
  583. List<float>[] points = { new List<float>(), new List<float>() };
  584. // Determine start points
  585. for (int curveIdx = 0; curveIdx < 2; curveIdx++)
  586. {
  587. float value = curves[curveIdx].Evaluate(time, false);
  588. points[curveIdx].Add(value);
  589. }
  590. times.Add(time);
  591. float rangeEnd = rangeOffset + range;
  592. while (time < rangeEnd)
  593. {
  594. float nextTime = time + timePerSample;
  595. bool hasStep = false;
  596. // Determine time to sample at. Use fixed increments unless there's a step keyframe within our increment in
  597. // which case we use its time so we can evaluate it directly
  598. for (int curveIdx = 0; curveIdx < 2; curveIdx++)
  599. {
  600. int keyframeIdx = keyframeIndices[curveIdx];
  601. if (keyframeIdx < keyframes[curveIdx].Length)
  602. {
  603. KeyFrame keyframe = keyframes[curveIdx][keyframeIdx];
  604. bool isStep = keyframe.inTangent == float.PositiveInfinity ||
  605. keyframe.outTangent == float.PositiveInfinity;
  606. if (isStep && keyframe.time <= nextTime)
  607. {
  608. nextTime = Math.Min(nextTime, keyframe.time);
  609. hasStep = true;
  610. }
  611. }
  612. }
  613. // Evaluate
  614. if (hasStep)
  615. {
  616. for (int curveIdx = 0; curveIdx < 2; curveIdx++)
  617. {
  618. int keyframeIdx = keyframeIndices[curveIdx];
  619. if (keyframeIdx < keyframes[curveIdx].Length)
  620. {
  621. KeyFrame keyframe = keyframes[curveIdx][keyframeIdx];
  622. if (MathEx.ApproxEquals(keyframe.time, nextTime))
  623. {
  624. if (keyframeIdx > 0)
  625. {
  626. KeyFrame prevKeyframe = keyframes[curveIdx][keyframeIdx - 1];
  627. points[curveIdx].Add(prevKeyframe.value);
  628. }
  629. else
  630. points[curveIdx].Add(keyframe.value);
  631. points[curveIdx].Add(keyframe.value);
  632. }
  633. else
  634. {
  635. // The other curve has step but this one doesn't, we just insert the same value twice
  636. float value = curves[curveIdx].Evaluate(nextTime, false);
  637. points[curveIdx].Add(value);
  638. points[curveIdx].Add(value);
  639. }
  640. times.Add(nextTime);
  641. times.Add(nextTime);
  642. }
  643. }
  644. }
  645. else
  646. {
  647. for (int curveIdx = 0; curveIdx < 2; curveIdx++)
  648. points[curveIdx].Add(curves[curveIdx].Evaluate(nextTime, false));
  649. times.Add(nextTime);
  650. }
  651. // Advance keyframe indices
  652. for (int curveIdx = 0; curveIdx < 2; curveIdx++)
  653. {
  654. int keyframeIdx = keyframeIndices[curveIdx];
  655. while (keyframeIdx < keyframes[curveIdx].Length)
  656. {
  657. KeyFrame keyframe = keyframes[curveIdx][keyframeIdx];
  658. if (keyframe.time > nextTime)
  659. break;
  660. keyframeIdx = ++keyframeIndices[curveIdx];
  661. }
  662. }
  663. time = nextTime;
  664. }
  665. // End points
  666. for (int curveIdx = 0; curveIdx < 2; curveIdx++)
  667. {
  668. float value = curves[curveIdx].Evaluate(rangeEnd, false);
  669. points[curveIdx].Add(value);
  670. }
  671. times.Add(rangeEnd);
  672. int numQuads = times.Count - 1;
  673. List<Vector2I> vertices = new List<Vector2I>();
  674. for (int i = 0; i < numQuads; i++)
  675. {
  676. int idxLeft = points[0][i] < points[1][i] ? 0 : 1;
  677. int idxRight = points[0][i + 1] < points[1][i + 1] ? 0 : 1;
  678. Vector2[] left =
  679. {
  680. new Vector2(times[i], points[0][i]),
  681. new Vector2(times[i], points[1][i])
  682. };
  683. Vector2[] right =
  684. {
  685. new Vector2(times[i + 1], points[0][i + 1]),
  686. new Vector2(times[i + 1], points[1][i + 1])
  687. };
  688. if (idxLeft == idxRight)
  689. {
  690. int idxA = idxLeft;
  691. int idxB = (idxLeft + 1) % 2;
  692. vertices.Add(CurveToPixelSpace(left[idxB]));
  693. vertices.Add(CurveToPixelSpace(right[idxB]));
  694. vertices.Add(CurveToPixelSpace(left[idxA]));
  695. vertices.Add(CurveToPixelSpace(right[idxB]));
  696. vertices.Add(CurveToPixelSpace(right[idxA]));
  697. vertices.Add(CurveToPixelSpace(left[idxA]));
  698. }
  699. // Lines intersects, can't represent them with a single quad
  700. else if (idxLeft != idxRight)
  701. {
  702. int idxA = idxLeft;
  703. int idxB = (idxLeft + 1) % 2;
  704. Line2 lineA = new Line2(left[idxB], right[idxA] - left[idxB]);
  705. Line2 lineB = new Line2(left[idxA], right[idxB] - left[idxA]);
  706. if (lineA.Intersects(lineB, out var t))
  707. {
  708. Vector2 intersection = left[idxB] + t * (right[idxA] - left[idxB]);
  709. vertices.Add(CurveToPixelSpace(left[idxB]));
  710. vertices.Add(CurveToPixelSpace(intersection));
  711. vertices.Add(CurveToPixelSpace(left[idxA]));
  712. vertices.Add(CurveToPixelSpace(intersection));
  713. vertices.Add(CurveToPixelSpace(right[idxB]));
  714. vertices.Add(CurveToPixelSpace(right[idxA]));
  715. }
  716. }
  717. }
  718. canvas.DrawTriangleList(vertices.ToArray(), color, 129);
  719. }
  720. }
  721. /// <summary>
  722. /// Information necessary to draw a curve.
  723. /// </summary>
  724. internal struct CurveDrawInfo
  725. {
  726. public CurveDrawInfo(EdAnimationCurve curve, Color color)
  727. {
  728. this.curve = curve;
  729. this.color = color;
  730. }
  731. public EdAnimationCurve curve;
  732. public Color color;
  733. }
  734. /** }@ */
  735. }