GUICurveDrawing.cs 34 KB

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