259 lines
9.4 KiB

  1. /*******************************************************************************************
  2. *
  3. * raylib [textures] example - Draw a texture along a segmented curve
  4. *
  5. * Example originally created with raylib 4.5-dev
  6. *
  7. * Example contributed by Jeffery Myers and reviewed by Ramon Santamaria (@raysan5)
  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) 2022-2023 Jeffery Myers and Ramon Santamaria (@raysan5)
  13. *
  14. ********************************************************************************************/
  15. #include "raylib.h"
  16. #include "raymath.h"
  17. #include "rlgl.h"
  18. #include <math.h> // Required for: powf()
  19. #include <stdlib.h> // Required for: NULL
  20. //----------------------------------------------------------------------------------
  21. // Global Variables Definition
  22. //----------------------------------------------------------------------------------
  23. static Texture texRoad = { 0 };
  24. static bool showCurve = false;
  25. static float curveWidth = 50;
  26. static int curveSegments = 24;
  27. static Vector2 curveStartPosition = { 0 };
  28. static Vector2 curveStartPositionTangent = { 0 };
  29. static Vector2 curveEndPosition = { 0 };
  30. static Vector2 curveEndPositionTangent = { 0 };
  31. static Vector2 *curveSelectedPoint = NULL;
  32. //----------------------------------------------------------------------------------
  33. // Module Functions Declaration
  34. //----------------------------------------------------------------------------------
  35. static void UpdateOptions(void);
  36. static void UpdateCurve(void);
  37. static void DrawCurve(void);
  38. static void DrawTexturedCurve(void);
  39. //------------------------------------------------------------------------------------
  40. // Program main entry point
  41. //------------------------------------------------------------------------------------
  42. int main()
  43. {
  44. // Initialization
  45. //--------------------------------------------------------------------------------------
  46. const int screenWidth = 800;
  47. const int screenHeight = 450;
  48. SetConfigFlags(FLAG_VSYNC_HINT | FLAG_MSAA_4X_HINT);
  49. InitWindow(screenWidth, screenHeight, "raylib [textures] examples - textured curve");
  50. // Load the road texture
  51. texRoad = LoadTexture("resources/road.png");
  52. SetTextureFilter(texRoad, TEXTURE_FILTER_BILINEAR);
  53. // Setup the curve
  54. curveStartPosition = (Vector2){ 80, 100 };
  55. curveStartPositionTangent = (Vector2){ 100, 300 };
  56. curveEndPosition = (Vector2){ 700, 350 };
  57. curveEndPositionTangent = (Vector2){ 600, 100 };
  58. SetTargetFPS(60); // Set our game to run at 60 frames-per-second
  59. //--------------------------------------------------------------------------------------
  60. // Main game loop
  61. while (!WindowShouldClose()) // Detect window close button or ESC key
  62. {
  63. // Update
  64. //----------------------------------------------------------------------------------
  65. UpdateCurve();
  66. UpdateOptions();
  67. //----------------------------------------------------------------------------------
  68. // Draw
  69. //----------------------------------------------------------------------------------
  70. BeginDrawing();
  71. ClearBackground(RAYWHITE);
  72. DrawTexturedCurve();
  73. DrawCurve();
  74. DrawText("Drag points to move curve, press SPACE to show/hide base curve", 10, 10, 10, DARKGRAY);
  75. DrawText(TextFormat("Curve width: %2.0f (Use + and - to adjust)", curveWidth), 10, 30, 10, DARKGRAY);
  76. DrawText(TextFormat("Curve segments: %d (Use LEFT and RIGHT to adjust)", curveSegments), 10, 50, 10, DARKGRAY);
  77. EndDrawing();
  78. //----------------------------------------------------------------------------------
  79. }
  80. // De-Initialization
  81. //--------------------------------------------------------------------------------------
  82. UnloadTexture(texRoad);
  83. CloseWindow(); // Close window and OpenGL context
  84. //--------------------------------------------------------------------------------------
  85. return 0;
  86. }
  87. //----------------------------------------------------------------------------------
  88. // Module Functions Definition
  89. //----------------------------------------------------------------------------------
  90. static void DrawCurve(void)
  91. {
  92. if (showCurve) DrawLineBezierCubic(curveStartPosition, curveEndPosition, curveStartPositionTangent, curveEndPositionTangent, 2, BLUE);
  93. // Draw the various control points and highlight where the mouse is
  94. DrawLineV(curveStartPosition, curveStartPositionTangent, SKYBLUE);
  95. DrawLineV(curveEndPosition, curveEndPositionTangent, PURPLE);
  96. Vector2 mouse = GetMousePosition();
  97. if (CheckCollisionPointCircle(mouse, curveStartPosition, 6)) DrawCircleV(curveStartPosition, 7, YELLOW);
  98. DrawCircleV(curveStartPosition, 5, RED);
  99. if (CheckCollisionPointCircle(mouse, curveStartPositionTangent, 6)) DrawCircleV(curveStartPositionTangent, 7, YELLOW);
  100. DrawCircleV(curveStartPositionTangent, 5, MAROON);
  101. if (CheckCollisionPointCircle(mouse, curveEndPosition, 6)) DrawCircleV(curveEndPosition, 7, YELLOW);
  102. DrawCircleV(curveEndPosition, 5, GREEN);
  103. if (CheckCollisionPointCircle(mouse, curveEndPositionTangent, 6)) DrawCircleV(curveEndPositionTangent, 7, YELLOW);
  104. DrawCircleV(curveEndPositionTangent, 5, DARKGREEN);
  105. }
  106. static void UpdateCurve(void)
  107. {
  108. // If the mouse is not down, we are not editing the curve so clear the selection
  109. if (!IsMouseButtonDown(MOUSE_LEFT_BUTTON))
  110. {
  111. curveSelectedPoint = NULL;
  112. return;
  113. }
  114. // If a point was selected, move it
  115. if (curveSelectedPoint)
  116. {
  117. *curveSelectedPoint = Vector2Add(*curveSelectedPoint, GetMouseDelta());
  118. return;
  119. }
  120. // The mouse is down, and nothing was selected, so see if anything was picked
  121. Vector2 mouse = GetMousePosition();
  122. if (CheckCollisionPointCircle(mouse, curveStartPosition, 6)) curveSelectedPoint = &curveStartPosition;
  123. else if (CheckCollisionPointCircle(mouse, curveStartPositionTangent, 6)) curveSelectedPoint = &curveStartPositionTangent;
  124. else if (CheckCollisionPointCircle(mouse, curveEndPosition, 6)) curveSelectedPoint = &curveEndPosition;
  125. else if (CheckCollisionPointCircle(mouse, curveEndPositionTangent, 6)) curveSelectedPoint = &curveEndPositionTangent;
  126. }
  127. static void DrawTexturedCurve(void)
  128. {
  129. const float step = 1.0f/curveSegments;
  130. Vector2 previous = curveStartPosition;
  131. Vector2 previousTangent = { 0 };
  132. float previousV = 0;
  133. // We can't compute a tangent for the first point, so we need to reuse the tangent from the first segment
  134. bool tangentSet = false;
  135. Vector2 current = { 0 };
  136. float t = 0.0f;
  137. for (int i = 1; i <= curveSegments; i++)
  138. {
  139. // Segment the curve
  140. t = step*i;
  141. float a = powf(1 - t, 3);
  142. float b = 3*powf(1 - t, 2)*t;
  143. float c = 3*(1 - t)*powf(t, 2);
  144. float d = powf(t, 3);
  145. // Compute the endpoint for this segment
  146. current.y = a*curveStartPosition.y + b*curveStartPositionTangent.y + c*curveEndPositionTangent.y + d*curveEndPosition.y;
  147. current.x = a*curveStartPosition.x + b*curveStartPositionTangent.x + c*curveEndPositionTangent.x + d*curveEndPosition.x;
  148. // Vector from previous to current
  149. Vector2 delta = { current.x - previous.x, current.y - previous.y };
  150. // The right hand normal to the delta vector
  151. Vector2 normal = Vector2Normalize((Vector2){ -delta.y, delta.x });
  152. // The v texture coordinate of the segment (add up the length of all the segments so far)
  153. float v = previousV + Vector2Length(delta);
  154. // Make sure the start point has a normal
  155. if (!tangentSet)
  156. {
  157. previousTangent = normal;
  158. tangentSet = true;
  159. }
  160. // Extend out the normals from the previous and current points to get the quad for this segment
  161. Vector2 prevPosNormal = Vector2Add(previous, Vector2Scale(previousTangent, curveWidth));
  162. Vector2 prevNegNormal = Vector2Add(previous, Vector2Scale(previousTangent, -curveWidth));
  163. Vector2 currentPosNormal = Vector2Add(current, Vector2Scale(normal, curveWidth));
  164. Vector2 currentNegNormal = Vector2Add(current, Vector2Scale(normal, -curveWidth));
  165. // Draw the segment as a quad
  166. rlSetTexture(texRoad.id);
  167. rlBegin(RL_QUADS);
  168. rlColor4ub(255,255,255,255);
  169. rlNormal3f(0.0f, 0.0f, 1.0f);
  170. rlTexCoord2f(0, previousV);
  171. rlVertex2f(prevNegNormal.x, prevNegNormal.y);
  172. rlTexCoord2f(1, previousV);
  173. rlVertex2f(prevPosNormal.x, prevPosNormal.y);
  174. rlTexCoord2f(1, v);
  175. rlVertex2f(currentPosNormal.x, currentPosNormal.y);
  176. rlTexCoord2f(0, v);
  177. rlVertex2f(currentNegNormal.x, currentNegNormal.y);
  178. rlEnd();
  179. // The current step is the start of the next step
  180. previous = current;
  181. previousTangent = normal;
  182. previousV = v;
  183. }
  184. }
  185. static void UpdateOptions(void)
  186. {
  187. if (IsKeyPressed(KEY_SPACE)) showCurve = !showCurve;
  188. // Update with
  189. if (IsKeyPressed(KEY_EQUAL)) curveWidth += 2;
  190. if (IsKeyPressed(KEY_MINUS)) curveWidth -= 2;
  191. if (curveWidth < 2) curveWidth = 2;
  192. // Update segments
  193. if (IsKeyPressed(KEY_LEFT)) curveSegments -= 2;
  194. if (IsKeyPressed(KEY_RIGHT)) curveSegments += 2;
  195. if (curveSegments < 2) curveSegments = 2;
  196. }