shapes_splines_drawing.c 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. /*******************************************************************************************
  2. *
  3. * raylib [shapes] example - splines drawing
  4. *
  5. * Example complexity rating: [★★★☆] 3/4
  6. *
  7. * Example originally created with raylib 5.0, last time updated with raylib 5.0
  8. *
  9. * Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
  10. * BSD-like license that allows static linking with closed source software
  11. *
  12. * Copyright (c) 2023-2025 Ramon Santamaria (@raysan5)
  13. *
  14. ********************************************************************************************/
  15. #include "raylib.h"
  16. #define RAYGUI_IMPLEMENTATION
  17. #include "raygui.h" // Required for UI controls
  18. #include <stdlib.h> // Required for: NULL
  19. #define MAX_SPLINE_POINTS 32
  20. //----------------------------------------------------------------------------------
  21. // Types and Structures Definition
  22. //----------------------------------------------------------------------------------
  23. // Cubic Bezier spline control points
  24. // NOTE: Every segment has two control points
  25. typedef struct {
  26. Vector2 start;
  27. Vector2 end;
  28. } ControlPoint;
  29. // Spline types
  30. typedef enum {
  31. SPLINE_LINEAR = 0, // Linear
  32. SPLINE_BASIS, // B-Spline
  33. SPLINE_CATMULLROM, // Catmull-Rom
  34. SPLINE_BEZIER // Cubic Bezier
  35. } SplineType;
  36. //------------------------------------------------------------------------------------
  37. // Program main entry point
  38. //------------------------------------------------------------------------------------
  39. int main(void)
  40. {
  41. // Initialization
  42. //--------------------------------------------------------------------------------------
  43. const int screenWidth = 800;
  44. const int screenHeight = 450;
  45. SetConfigFlags(FLAG_MSAA_4X_HINT);
  46. InitWindow(screenWidth, screenHeight, "raylib [shapes] example - splines drawing");
  47. Vector2 points[MAX_SPLINE_POINTS] = {
  48. { 50.0f, 400.0f },
  49. { 160.0f, 220.0f },
  50. { 340.0f, 380.0f },
  51. { 520.0f, 60.0f },
  52. { 710.0f, 260.0f },
  53. };
  54. // Array required for spline bezier-cubic,
  55. // including control points interleaved with start-end segment points
  56. Vector2 pointsInterleaved[3*(MAX_SPLINE_POINTS - 1) + 1] = { 0 };
  57. int pointCount = 5;
  58. int selectedPoint = -1;
  59. int focusedPoint = -1;
  60. Vector2 *selectedControlPoint = NULL;
  61. Vector2 *focusedControlPoint = NULL;
  62. // Cubic Bezier control points initialization
  63. ControlPoint control[MAX_SPLINE_POINTS-1] = { 0 };
  64. for (int i = 0; i < pointCount - 1; i++)
  65. {
  66. control[i].start = (Vector2){ points[i].x + 50, points[i].y };
  67. control[i].end = (Vector2){ points[i + 1].x - 50, points[i + 1].y };
  68. }
  69. // Spline config variables
  70. float splineThickness = 8.0f;
  71. int splineTypeActive = SPLINE_LINEAR; // 0-Linear, 1-BSpline, 2-CatmullRom, 3-Bezier
  72. bool splineTypeEditMode = false;
  73. bool splineHelpersActive = true;
  74. SetTargetFPS(60); // Set our game to run at 60 frames-per-second
  75. //--------------------------------------------------------------------------------------
  76. // Main game loop
  77. while (!WindowShouldClose()) // Detect window close button or ESC key
  78. {
  79. // Update
  80. //----------------------------------------------------------------------------------
  81. // Spline points creation logic (at the end of spline)
  82. if (IsMouseButtonPressed(MOUSE_RIGHT_BUTTON) && (pointCount < MAX_SPLINE_POINTS))
  83. {
  84. points[pointCount] = GetMousePosition();
  85. int i = pointCount - 1;
  86. control[i].start = (Vector2){ points[i].x + 50, points[i].y };
  87. control[i].end = (Vector2){ points[i + 1].x - 50, points[i + 1].y };
  88. pointCount++;
  89. }
  90. // Spline point focus and selection logic
  91. if ((selectedPoint == -1) && ((splineTypeActive != SPLINE_BEZIER) || (selectedControlPoint == NULL)))
  92. {
  93. focusedPoint = -1;
  94. for (int i = 0; i < pointCount; i++)
  95. {
  96. if (CheckCollisionPointCircle(GetMousePosition(), points[i], 8.0f))
  97. {
  98. focusedPoint = i;
  99. break;
  100. }
  101. }
  102. if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) selectedPoint = focusedPoint;
  103. }
  104. // Spline point movement logic
  105. if (selectedPoint >= 0)
  106. {
  107. points[selectedPoint] = GetMousePosition();
  108. if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) selectedPoint = -1;
  109. }
  110. // Cubic Bezier spline control points logic
  111. if ((splineTypeActive == SPLINE_BEZIER) && (focusedPoint == -1))
  112. {
  113. // Spline control point focus and selection logic
  114. if (selectedControlPoint == NULL)
  115. {
  116. focusedControlPoint = NULL;
  117. for (int i = 0; i < pointCount - 1; i++)
  118. {
  119. if (CheckCollisionPointCircle(GetMousePosition(), control[i].start, 6.0f))
  120. {
  121. focusedControlPoint = &control[i].start;
  122. break;
  123. }
  124. else if (CheckCollisionPointCircle(GetMousePosition(), control[i].end, 6.0f))
  125. {
  126. focusedControlPoint = &control[i].end;
  127. break;
  128. }
  129. }
  130. if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) selectedControlPoint = focusedControlPoint;
  131. }
  132. // Spline control point movement logic
  133. if (selectedControlPoint != NULL)
  134. {
  135. *selectedControlPoint = GetMousePosition();
  136. if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) selectedControlPoint = NULL;
  137. }
  138. }
  139. // Spline selection logic
  140. if (IsKeyPressed(KEY_ONE)) splineTypeActive = 0;
  141. else if (IsKeyPressed(KEY_TWO)) splineTypeActive = 1;
  142. else if (IsKeyPressed(KEY_THREE)) splineTypeActive = 2;
  143. else if (IsKeyPressed(KEY_FOUR)) splineTypeActive = 3;
  144. // Clear selection when changing to a spline without control points
  145. if (IsKeyPressed(KEY_ONE) || IsKeyPressed(KEY_TWO) || IsKeyPressed(KEY_THREE)) selectedControlPoint = NULL;
  146. //----------------------------------------------------------------------------------
  147. // Draw
  148. //----------------------------------------------------------------------------------
  149. BeginDrawing();
  150. ClearBackground(RAYWHITE);
  151. if (splineTypeActive == SPLINE_LINEAR)
  152. {
  153. // Draw spline: linear
  154. DrawSplineLinear(points, pointCount, splineThickness, RED);
  155. }
  156. else if (splineTypeActive == SPLINE_BASIS)
  157. {
  158. // Draw spline: basis
  159. DrawSplineBasis(points, pointCount, splineThickness, RED); // Provide connected points array
  160. /*
  161. for (int i = 0; i < (pointCount - 3); i++)
  162. {
  163. // Drawing individual segments, not considering thickness connection compensation
  164. DrawSplineSegmentBasis(points[i], points[i + 1], points[i + 2], points[i + 3], splineThickness, MAROON);
  165. }
  166. */
  167. }
  168. else if (splineTypeActive == SPLINE_CATMULLROM)
  169. {
  170. // Draw spline: catmull-rom
  171. DrawSplineCatmullRom(points, pointCount, splineThickness, RED); // Provide connected points array
  172. /*
  173. for (int i = 0; i < (pointCount - 3); i++)
  174. {
  175. // Drawing individual segments, not considering thickness connection compensation
  176. DrawSplineSegmentCatmullRom(points[i], points[i + 1], points[i + 2], points[i + 3], splineThickness, MAROON);
  177. }
  178. */
  179. }
  180. else if (splineTypeActive == SPLINE_BEZIER)
  181. {
  182. // NOTE: Cubic-bezier spline requires the 2 control points of each segnment to be
  183. // provided interleaved with the start and end point of every segment
  184. for (int i = 0; i < (pointCount - 1); i++)
  185. {
  186. pointsInterleaved[3*i] = points[i];
  187. pointsInterleaved[3*i + 1] = control[i].start;
  188. pointsInterleaved[3*i + 2] = control[i].end;
  189. }
  190. pointsInterleaved[3*(pointCount - 1)] = points[pointCount - 1];
  191. // Draw spline: cubic-bezier (with control points)
  192. DrawSplineBezierCubic(pointsInterleaved, 3*(pointCount - 1) + 1, splineThickness, RED);
  193. /*
  194. for (int i = 0; i < 3*(pointCount - 1); i += 3)
  195. {
  196. // Drawing individual segments, not considering thickness connection compensation
  197. DrawSplineSegmentBezierCubic(pointsInterleaved[i], pointsInterleaved[i + 1], pointsInterleaved[i + 2], pointsInterleaved[i + 3], splineThickness, MAROON);
  198. }
  199. */
  200. // Draw spline control points
  201. for (int i = 0; i < pointCount - 1; i++)
  202. {
  203. // Every cubic bezier point have two control points
  204. DrawCircleV(control[i].start, 6, GOLD);
  205. DrawCircleV(control[i].end, 6, GOLD);
  206. if (focusedControlPoint == &control[i].start) DrawCircleV(control[i].start, 8, GREEN);
  207. else if (focusedControlPoint == &control[i].end) DrawCircleV(control[i].end, 8, GREEN);
  208. DrawLineEx(points[i], control[i].start, 1.0f, LIGHTGRAY);
  209. DrawLineEx(points[i + 1], control[i].end, 1.0f, LIGHTGRAY);
  210. // Draw spline control lines
  211. DrawLineV(points[i], control[i].start, GRAY);
  212. //DrawLineV(control[i].start, control[i].end, LIGHTGRAY);
  213. DrawLineV(control[i].end, points[i + 1], GRAY);
  214. }
  215. }
  216. if (splineHelpersActive)
  217. {
  218. // Draw spline point helpers
  219. for (int i = 0; i < pointCount; i++)
  220. {
  221. DrawCircleLinesV(points[i], (focusedPoint == i)? 12.0f : 8.0f, (focusedPoint == i)? BLUE: DARKBLUE);
  222. if ((splineTypeActive != SPLINE_LINEAR) &&
  223. (splineTypeActive != SPLINE_BEZIER) &&
  224. (i < pointCount - 1)) DrawLineV(points[i], points[i + 1], GRAY);
  225. DrawText(TextFormat("[%.0f, %.0f]", points[i].x, points[i].y), (int)points[i].x, (int)points[i].y + 10, 10, BLACK);
  226. }
  227. }
  228. // Check all possible UI states that require controls lock
  229. if (splineTypeEditMode || (selectedPoint != -1) || (selectedControlPoint != NULL)) GuiLock();
  230. // Draw spline config
  231. GuiLabel((Rectangle){ 12, 62, 140, 24 }, TextFormat("Spline thickness: %i", (int)splineThickness));
  232. GuiSliderBar((Rectangle){ 12, 60 + 24, 140, 16 }, NULL, NULL, &splineThickness, 1.0f, 40.0f);
  233. GuiCheckBox((Rectangle){ 12, 110, 20, 20 }, "Show point helpers", &splineHelpersActive);
  234. if (splineTypeEditMode) GuiUnlock();
  235. GuiLabel((Rectangle){ 12, 10, 140, 24 }, "Spline type:");
  236. if (GuiDropdownBox((Rectangle){ 12, 8 + 24, 140, 28 }, "LINEAR;BSPLINE;CATMULLROM;BEZIER", &splineTypeActive, splineTypeEditMode)) splineTypeEditMode = !splineTypeEditMode;
  237. GuiUnlock();
  238. EndDrawing();
  239. //----------------------------------------------------------------------------------
  240. }
  241. // De-Initialization
  242. //--------------------------------------------------------------------------------------
  243. CloseWindow(); // Close window and OpenGL context
  244. //--------------------------------------------------------------------------------------
  245. return 0;
  246. }