GUICurveDrawing.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  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. // TODO DOC
  12. internal class GUICurveDrawing
  13. {
  14. private const int LINE_SPLIT_WIDTH = 2;
  15. private static readonly Color COLOR_MID_GRAY = new Color(90.0f / 255.0f, 90.0f / 255.0f, 90.0f / 255.0f, 1.0f);
  16. private static readonly Color COLOR_DARK_GRAY = new Color(40.0f / 255.0f, 40.0f / 255.0f, 40.0f / 255.0f, 1.0f);
  17. private EdAnimationCurve[] curves;
  18. private int width;
  19. private int height;
  20. private float xRange = 60.0f;
  21. private float yRange = 20.0f;
  22. private int fps = 1;
  23. private int markedFrameIdx = -1;
  24. private int drawableWidth;
  25. private GUICanvas canvas;
  26. public GUICurveDrawing(GUILayout layout, int width, int height, EdAnimationCurve[] curves)
  27. {
  28. canvas = new GUICanvas();
  29. layout.AddElement(canvas);
  30. this.curves = curves;
  31. SetSize(width, height);
  32. }
  33. public void SetCurves(EdAnimationCurve[] curves)
  34. {
  35. this.curves = curves;
  36. Rebuild();
  37. }
  38. public void SetSize(int width, int height)
  39. {
  40. this.width = width;
  41. this.height = height;
  42. canvas.SetWidth(width);
  43. canvas.SetHeight(height);
  44. drawableWidth = Math.Max(0, width - GUITimeline.PADDING * 2);
  45. Rebuild();
  46. }
  47. public void SetRange(float xRange, float yRange)
  48. {
  49. this.xRange = xRange;
  50. this.yRange = yRange;
  51. Rebuild();
  52. }
  53. public void SetFPS(int fps)
  54. {
  55. this.fps = Math.Max(1, fps);
  56. Rebuild();
  57. }
  58. // Set to -1 to clear it
  59. public void SetMarkedFrame(int frameIdx)
  60. {
  61. markedFrameIdx = frameIdx;
  62. Rebuild();
  63. }
  64. public bool GetCurveCoordinates(Vector2I windowCoords, out Vector2 curveCoords)
  65. {
  66. Rect2I bounds = canvas.Bounds;
  67. if (windowCoords.x < (bounds.x + GUITimeline.PADDING) || windowCoords.x >= (bounds.x + bounds.width - GUITimeline.PADDING) ||
  68. windowCoords.y < bounds.y || windowCoords.y >= (bounds.y + bounds.height))
  69. {
  70. curveCoords = new Vector2();
  71. return false;
  72. }
  73. Vector2I relativeCoords = windowCoords - new Vector2I(bounds.x + GUITimeline.PADDING, bounds.y);
  74. float lengthPerPixel = xRange / drawableWidth;
  75. float heightPerPixel = yRange / height;
  76. float yOffset = yRange/2.0f;
  77. curveCoords = new Vector2();
  78. curveCoords.x = relativeCoords.x * lengthPerPixel;
  79. curveCoords.y = yOffset - relativeCoords.y * heightPerPixel;
  80. return true;
  81. }
  82. private void DrawFrameMarker(float t, Color color)
  83. {
  84. int xPos = (int)((t / GetRange()) * drawableWidth) + GUITimeline.PADDING;
  85. Vector2I start = new Vector2I(xPos, 0);
  86. Vector2I end = new Vector2I(xPos, height);
  87. canvas.DrawLine(start, end, color);
  88. }
  89. private void DrawCenterLine()
  90. {
  91. int heightOffset = height / 2; // So that y = 0 is at center of canvas
  92. Vector2I start = new Vector2I(0, heightOffset);
  93. Vector2I end = new Vector2I(width, heightOffset);
  94. canvas.DrawLine(start, end, COLOR_DARK_GRAY);
  95. }
  96. // Returns range rounded to the nearest multiple of FPS
  97. private float GetRange()
  98. {
  99. float spf = 1.0f / fps;
  100. return ((int)xRange / spf) * spf;
  101. }
  102. private void Rebuild()
  103. {
  104. canvas.Clear();
  105. if (curves == null)
  106. return;
  107. float range = GetRange();
  108. int numFrames = (int)range * fps;
  109. float timePerFrame = range / numFrames;
  110. // Draw vertical frame markers
  111. float frameWidth = drawableWidth / (float)numFrames;
  112. int markerInterval = (int)Math.Max(1.0f, GUITimeline.OPTIMAL_TICK_WIDTH / frameWidth) * 5;
  113. //// Draw extra frames to prevent the out-of-frame ticks from popping in and out as range changes
  114. float extraWidth = GUITimeline.PADDING;
  115. numFrames += (int)(extraWidth / frameWidth);
  116. float t = 0.0f;
  117. for (int i = 0; i < numFrames; i += markerInterval)
  118. {
  119. DrawFrameMarker(t, COLOR_DARK_GRAY);
  120. t += timePerFrame * markerInterval;
  121. }
  122. // Draw center line
  123. DrawCenterLine();
  124. int idx = 0;
  125. foreach (var curve in curves)
  126. {
  127. Color color = GetUniqueColor(idx);
  128. DrawCurve(curve, color);
  129. idx++;
  130. }
  131. // Draw selected frame marker
  132. if (markedFrameIdx != -1)
  133. DrawFrameMarker(markedFrameIdx * timePerFrame, Color.Red);
  134. }
  135. private Color GetUniqueColor(int idx)
  136. {
  137. const int COLOR_SPACING = 359 / 15;
  138. float hue = ((idx * COLOR_SPACING) % 359) / 359.0f;
  139. return Color.HSV2RGB(new Color(hue, 175.0f / 255.0f, 175.0f / 255.0f));
  140. }
  141. private void DrawCurve(EdAnimationCurve curve, Color color)
  142. {
  143. float lengthPerPixel = xRange/drawableWidth;
  144. float pixelsPerHeight = height/yRange;
  145. int heightOffset = height/2; // So that y = 0 is at center of canvas
  146. KeyFrame[] keyframes = curve.Native.KeyFrames;
  147. if (keyframes.Length < 0)
  148. return;
  149. // Draw start line
  150. {
  151. float start = MathEx.Clamp(keyframes[0].time, 0.0f, xRange);
  152. int startPixel = (int)(start / lengthPerPixel);
  153. int xPosStart = 0;
  154. int xPosEnd = GUITimeline.PADDING + startPixel;
  155. int yPos = (int)(curve.Native.Evaluate(0.0f, false) * pixelsPerHeight);
  156. yPos = heightOffset - yPos; // Offset and flip height (canvas Y goes down)
  157. Vector2I a = new Vector2I(xPosStart, yPos);
  158. Vector2I b = new Vector2I(xPosEnd, yPos);
  159. canvas.DrawLine(a, b, COLOR_MID_GRAY);
  160. }
  161. List<Vector2I> linePoints = new List<Vector2I>();
  162. // Draw in between keyframes
  163. for (int i = 0; i < keyframes.Length - 1; i++)
  164. {
  165. float start = MathEx.Clamp(keyframes[i].time, 0.0f, xRange);
  166. float end = MathEx.Clamp(keyframes[i + 1].time, 0.0f, xRange);
  167. int startPixel = (int)(start / lengthPerPixel);
  168. int endPixel = (int)(end / lengthPerPixel);
  169. bool isStep = keyframes[i].outTangent == float.PositiveInfinity ||
  170. keyframes[i + 1].inTangent == float.PositiveInfinity;
  171. // If step tangent, draw the required lines without sampling, as the sampling will miss the step
  172. if (isStep)
  173. {
  174. // Line from left to right frame
  175. int xPos = startPixel;
  176. int yPosStart = (int)(curve.Native.Evaluate(start, false) * pixelsPerHeight);
  177. yPosStart = heightOffset - yPosStart; // Offset and flip height (canvas Y goes down)
  178. linePoints.Add(new Vector2I(GUITimeline.PADDING + xPos, yPosStart));
  179. xPos = endPixel;
  180. linePoints.Add(new Vector2I(GUITimeline.PADDING + xPos, yPosStart));
  181. // Line representing the step
  182. int yPosEnd = (int)(curve.Native.Evaluate(end, false) * pixelsPerHeight);
  183. yPosEnd = heightOffset - yPosEnd; // Offset and flip height (canvas Y goes down)
  184. linePoints.Add(new Vector2I(GUITimeline.PADDING + xPos, yPosEnd));
  185. }
  186. else // Draw normally
  187. {
  188. int numSplits;
  189. float timeIncrement;
  190. if (startPixel != endPixel)
  191. {
  192. float fNumSplits = (endPixel - startPixel)/(float) LINE_SPLIT_WIDTH;
  193. numSplits = MathEx.FloorToInt(fNumSplits);
  194. float remainder = fNumSplits - numSplits;
  195. float lengthRounded = (end - start)*(numSplits/fNumSplits);
  196. timeIncrement = lengthRounded/numSplits;
  197. numSplits += MathEx.CeilToInt(remainder) + 1;
  198. }
  199. else
  200. {
  201. numSplits = 1;
  202. timeIncrement = 0.0f;
  203. }
  204. for (int j = 0; j < numSplits; j++)
  205. {
  206. int xPos = Math.Min(startPixel + j * LINE_SPLIT_WIDTH, endPixel);
  207. float t = Math.Min(start + j * timeIncrement, end);
  208. int yPos = (int)(curve.Native.Evaluate(t, false) * pixelsPerHeight);
  209. yPos = heightOffset - yPos; // Offset and flip height (canvas Y goes down)
  210. linePoints.Add(new Vector2I(GUITimeline.PADDING + xPos, yPos));
  211. }
  212. }
  213. }
  214. canvas.DrawPolyLine(linePoints.ToArray(), color);
  215. // Draw end line
  216. {
  217. float end = MathEx.Clamp(keyframes[keyframes.Length - 1].time, 0.0f, xRange);
  218. int endPixel = (int)(end / lengthPerPixel);
  219. int xPosStart = GUITimeline.PADDING + endPixel;
  220. int xPosEnd = width;
  221. int yPos = (int)(curve.Native.Evaluate(xRange, false) * pixelsPerHeight);
  222. yPos = heightOffset - yPos; // Offset and flip height (canvas Y goes down)
  223. Vector2I a = new Vector2I(xPosStart, yPos);
  224. Vector2I b = new Vector2I(xPosEnd, yPos);
  225. canvas.DrawLine(a, b, COLOR_MID_GRAY);
  226. }
  227. }
  228. }
  229. /** }@ */
  230. }