shapes_penrose_tile.c 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. /*******************************************************************************************
  2. *
  3. * raylib [shapes] example - penrose tile
  4. *
  5. * Example complexity rating: [★★★★] 4/4
  6. *
  7. * Example originally created with raylib 5.5, last time updated with raylib 5.6-dev
  8. * Based on: https://processing.org/examples/penrosetile.html
  9. *
  10. * Example contributed by David Buzatto (@davidbuzatto) and reviewed by Ramon Santamaria (@raysan5)
  11. *
  12. * Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
  13. * BSD-like license that allows static linking with closed source software
  14. *
  15. * Copyright (c) 2025 David Buzatto (@davidbuzatto)
  16. *
  17. ********************************************************************************************/
  18. #include "raylib.h"
  19. #include <stdlib.h>
  20. #include <string.h>
  21. #include <math.h>
  22. #define STR_MAX_SIZE 10000
  23. #define TURTLE_STACK_MAX_SIZE 50
  24. //----------------------------------------------------------------------------------
  25. // Types and Structures Definition
  26. //----------------------------------------------------------------------------------
  27. typedef struct TurtleState {
  28. Vector2 origin;
  29. double angle;
  30. } TurtleState;
  31. typedef struct PenroseLSystem {
  32. int steps;
  33. char *production;
  34. const char *ruleW;
  35. const char *ruleX;
  36. const char *ruleY;
  37. const char *ruleZ;
  38. float drawLength;
  39. float theta;
  40. } PenroseLSystem;
  41. //----------------------------------------------------------------------------------
  42. // Global Variables Definition
  43. //----------------------------------------------------------------------------------
  44. static TurtleState turtleStack[TURTLE_STACK_MAX_SIZE];
  45. static int turtleTop = -1;
  46. //----------------------------------------------------------------------------------
  47. // Module Functions Declaration
  48. //----------------------------------------------------------------------------------
  49. static void PushTurtleState(TurtleState state);
  50. static TurtleState PopTurtleState(void);
  51. static PenroseLSystem CreatePenroseLSystem(float drawLength);
  52. static void BuildProductionStep(PenroseLSystem *ls);
  53. static void DrawPenroseLSystem(PenroseLSystem *ls);
  54. //------------------------------------------------------------------------------------
  55. // Program main entry point
  56. //------------------------------------------------------------------------------------
  57. int main(void)
  58. {
  59. // Initialization
  60. //--------------------------------------------------------------------------------------
  61. const int screenWidth = 800;
  62. const int screenHeight = 450;
  63. SetConfigFlags(FLAG_MSAA_4X_HINT);
  64. InitWindow(screenWidth, screenHeight, "raylib [shapes] example - penrose tile");
  65. float drawLength = 460.0f;
  66. int minGenerations = 0;
  67. int maxGenerations = 4;
  68. int generations = 0;
  69. // Initializee new penrose tile
  70. PenroseLSystem ls = CreatePenroseLSystem(drawLength*(generations/(float)maxGenerations));
  71. for (int i = 0; i < generations; i++) BuildProductionStep(&ls);
  72. SetTargetFPS(120); // Set our game to run at 120 frames-per-second
  73. //---------------------------------------------------------------------------------------
  74. // Main game loop
  75. while (!WindowShouldClose()) // Detect window close button or ESC key
  76. {
  77. // Update
  78. //----------------------------------------------------------------------------------
  79. bool rebuild = false;
  80. if (IsKeyPressed(KEY_UP))
  81. {
  82. if (generations < maxGenerations)
  83. {
  84. generations++;
  85. rebuild = true;
  86. }
  87. }
  88. else if (IsKeyPressed(KEY_DOWN))
  89. {
  90. if (generations > minGenerations)
  91. {
  92. generations--;
  93. if (generations > 0) rebuild = true;
  94. }
  95. }
  96. if (rebuild)
  97. {
  98. RL_FREE(ls.production); // Free previous production for re-creation
  99. ls = CreatePenroseLSystem(drawLength*(generations/(float)maxGenerations));
  100. for (int i = 0; i < generations; i++) BuildProductionStep(&ls);
  101. }
  102. //----------------------------------------------------------------------------------
  103. // Draw
  104. //----------------------------------------------------------------------------------
  105. BeginDrawing();
  106. ClearBackground( RAYWHITE );
  107. if (generations > 0) DrawPenroseLSystem(&ls);
  108. DrawText("penrose l-system", 10, 10, 20, DARKGRAY);
  109. DrawText("press up or down to change generations", 10, 30, 20, DARKGRAY);
  110. DrawText(TextFormat("generations: %d", generations), 10, 50, 20, DARKGRAY);
  111. EndDrawing();
  112. //----------------------------------------------------------------------------------
  113. }
  114. // De-Initialization
  115. //--------------------------------------------------------------------------------------
  116. CloseWindow(); // Close window and OpenGL context
  117. //--------------------------------------------------------------------------------------
  118. return 0;
  119. }
  120. //----------------------------------------------------------------------------------
  121. // Module Functions Definition
  122. //----------------------------------------------------------------------------------
  123. // Push turtle state for next step
  124. static void PushTurtleState(TurtleState state)
  125. {
  126. if (turtleTop < (TURTLE_STACK_MAX_SIZE - 1)) turtleStack[++turtleTop] = state;
  127. else TraceLog(LOG_WARNING, "TURTLE STACK OVERFLOW!");
  128. }
  129. // Pop turtle state step
  130. static TurtleState PopTurtleState(void)
  131. {
  132. if (turtleTop >= 0) return turtleStack[turtleTop--];
  133. else TraceLog(LOG_WARNING, "TURTLE STACK UNDERFLOW!");
  134. return (TurtleState){ 0 };
  135. }
  136. // Create a new penrose tile structure
  137. static PenroseLSystem CreatePenroseLSystem(float drawLength)
  138. {
  139. // TODO: Review constant values assignment on recreation?
  140. PenroseLSystem ls = {
  141. .steps = 0,
  142. .ruleW = "YF++ZF4-XF[-YF4-WF]++",
  143. .ruleX = "+YF--ZF[3-WF--XF]+",
  144. .ruleY = "-WF++XF[+++YF++ZF]-",
  145. .ruleZ = "--YF++++WF[+ZF++++XF]--XF",
  146. .drawLength = drawLength,
  147. .theta = 36.0f // Degrees
  148. };
  149. ls.production = (char *)RL_MALLOC(sizeof(char)*STR_MAX_SIZE);
  150. ls.production[0] = '\0';
  151. strncpy(ls.production, "[X]++[X]++[X]++[X]++[X]", STR_MAX_SIZE);
  152. return ls;
  153. }
  154. // Build next penrose step
  155. static void BuildProductionStep(PenroseLSystem *ls)
  156. {
  157. char *newProduction = (char *)RL_MALLOC(sizeof(char)*STR_MAX_SIZE);
  158. newProduction[0] = '\0';
  159. int productionLength = strnlen(ls->production, STR_MAX_SIZE);
  160. for (int i = 0; i < productionLength; i++)
  161. {
  162. char step = ls->production[i];
  163. int remainingSpace = STR_MAX_SIZE - strnlen(newProduction, STR_MAX_SIZE) - 1;
  164. switch (step)
  165. {
  166. case 'W': strncat(newProduction, ls->ruleW, remainingSpace); break;
  167. case 'X': strncat(newProduction, ls->ruleX, remainingSpace); break;
  168. case 'Y': strncat(newProduction, ls->ruleY, remainingSpace); break;
  169. case 'Z': strncat(newProduction, ls->ruleZ, remainingSpace); break;
  170. default:
  171. {
  172. if (step != 'F')
  173. {
  174. int t = strnlen(newProduction, STR_MAX_SIZE);
  175. newProduction[t] = step;
  176. newProduction[t + 1] = '\0';
  177. }
  178. } break;
  179. }
  180. }
  181. ls->drawLength *= 0.5f;
  182. strncpy(ls->production, newProduction, STR_MAX_SIZE);
  183. RL_FREE(newProduction);
  184. }
  185. // Draw penrose tile lines
  186. static void DrawPenroseLSystem(PenroseLSystem *ls)
  187. {
  188. Vector2 screenCenter = { GetScreenWidth()/2, GetScreenHeight()/2 };
  189. TurtleState turtle = {
  190. .origin = { 0 },
  191. .angle = -90.0f
  192. };
  193. int repeats = 1;
  194. int productionLength = (int)strnlen(ls->production, STR_MAX_SIZE);
  195. ls->steps += 12;
  196. if (ls->steps > productionLength) ls->steps = productionLength;
  197. for (int i = 0; i < ls->steps; i++)
  198. {
  199. char step = ls->production[i];
  200. if (step == 'F')
  201. {
  202. for (int j = 0; j < repeats; j++)
  203. {
  204. Vector2 startPosWorld = turtle.origin;
  205. float radAngle = DEG2RAD*turtle.angle;
  206. turtle.origin.x += ls->drawLength*cosf(radAngle);
  207. turtle.origin.y += ls->drawLength*sinf(radAngle);
  208. Vector2 startPosScreen = { startPosWorld.x + screenCenter.x, startPosWorld.y + screenCenter.y };
  209. Vector2 endPosScreen = { turtle.origin.x + screenCenter.x, turtle.origin.y + screenCenter.y };
  210. DrawLineEx(startPosScreen, endPosScreen, 2, Fade(BLACK, 0.2));
  211. }
  212. repeats = 1;
  213. }
  214. else if (step == '+')
  215. {
  216. for (int j = 0; j < repeats; j++) turtle.angle += ls->theta;
  217. repeats = 1;
  218. }
  219. else if (step == '-')
  220. {
  221. for (int j = 0; j < repeats; j++) turtle.angle += -ls->theta;
  222. repeats = 1;
  223. }
  224. else if (step == '[') PushTurtleState(turtle);
  225. else if (step == ']') turtle = PopTurtleState();
  226. else if ((step >= 48) && (step <= 57)) repeats = (int) step - 48;
  227. }
  228. turtleTop = -1;
  229. }