1+ /*******************************************************************************************
2+ *
3+ * raylib [shapes] example - penrose tile
4+ *
5+ * Example complexity rating: [★★★★] 4/4
6+ *
7+ * Example originally created with raylib 5.5
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+
19+ #include <stdlib.h>
20+ #include <string.h>
21+ #include <math.h>
22+ #include "raylib.h"
23+
24+ #define STR_MAX_SIZE 10000
25+ #define TURTLE_STACK_MAX_SIZE 50
26+
27+ typedef struct TurtleState {
28+ Vector2 origin ;
29+ double angle ;
30+ } TurtleState ;
31+
32+ typedef struct PenroseLSystem {
33+ int steps ;
34+ char * production ;
35+ const char * ruleW ;
36+ const char * ruleX ;
37+ const char * ruleY ;
38+ const char * ruleZ ;
39+ float drawLength ;
40+ float theta ;
41+ } PenroseLSystem ;
42+
43+ static TurtleState turtleStack [TURTLE_STACK_MAX_SIZE ];
44+ static int turtleTop = -1 ;
45+
46+ void PushTurtleState (TurtleState state )
47+ {
48+ if (turtleTop < TURTLE_STACK_MAX_SIZE - 1 )
49+ {
50+ turtleStack [++ turtleTop ] = state ;
51+ }
52+ else
53+ {
54+ TraceLog (LOG_WARNING , "TURTLE STACK OVERFLOW!" );
55+ }
56+ }
57+
58+ TurtleState PopTurtleState (void )
59+ {
60+ if (turtleTop >= 0 )
61+ {
62+ return turtleStack [turtleTop -- ];
63+ }
64+ else
65+ {
66+ TraceLog (LOG_WARNING , "TURTLE STACK UNDERFLOW!" );
67+ }
68+ return (TurtleState ) {0 };
69+ }
70+
71+ PenroseLSystem CreatePenroseLSystem (float drawLength )
72+ {
73+ PenroseLSystem ls = {
74+ .steps = 0 ,
75+ .ruleW = "YF++ZF4-XF[-YF4-WF]++" ,
76+ .ruleX = "+YF--ZF[3-WF--XF]+" ,
77+ .ruleY = "-WF++XF[+++YF++ZF]-" ,
78+ .ruleZ = "--YF++++WF[+ZF++++XF]--XF" ,
79+ .drawLength = drawLength ,
80+ .theta = 36.0f // in degrees
81+ };
82+ ls .production = (char * ) malloc (sizeof (char ) * STR_MAX_SIZE );
83+ ls .production [0 ] = '\0' ;
84+ strncpy (ls .production , "[X]++[X]++[X]++[X]++[X]" , STR_MAX_SIZE );
85+ return ls ;
86+ }
87+
88+ void DrawPenroseLSystem (PenroseLSystem * ls )
89+ {
90+ Vector2 screenCenter = {GetScreenWidth ()/2 , GetScreenHeight ()/2 };
91+
92+ TurtleState turtle = {
93+ .origin = {0 },
94+ .angle = -90.0f
95+ };
96+
97+ int repeats = 1 ;
98+ int productionLength = (int ) strnlen (ls -> production , STR_MAX_SIZE );
99+ ls -> steps += 12 ;
100+
101+ if (ls -> steps > productionLength )
102+ {
103+ ls -> steps = productionLength ;
104+ }
105+
106+ for (int i = 0 ; i < ls -> steps ; i ++ )
107+ {
108+ char step = ls -> production [i ];
109+ if ( step == 'F' )
110+ {
111+ for ( int j = 0 ; j < repeats ; j ++ )
112+ {
113+ Vector2 startPosWorld = turtle .origin ;
114+ float radAngle = DEG2RAD * turtle .angle ;
115+ turtle .origin .x += ls -> drawLength * cosf (radAngle );
116+ turtle .origin .y += ls -> drawLength * sinf (radAngle );
117+ Vector2 startPosScreen = {startPosWorld .x + screenCenter .x , startPosWorld .y + screenCenter .y };
118+ Vector2 endPosScreen = {turtle .origin .x + screenCenter .x , turtle .origin .y + screenCenter .y };
119+ DrawLineEx (startPosScreen , endPosScreen , 2 , Fade (BLACK , 0.2 ));
120+ }
121+ repeats = 1 ;
122+ }
123+ else if ( step == '+' )
124+ {
125+ for ( int j = 0 ; j < repeats ; j ++ )
126+ {
127+ turtle .angle += ls -> theta ;
128+ }
129+ repeats = 1 ;
130+ }
131+ else if ( step == '-' )
132+ {
133+ for ( int j = 0 ; j < repeats ; j ++ )
134+ {
135+ turtle .angle += - ls -> theta ;
136+ }
137+ repeats = 1 ;
138+ }
139+ else if ( step == '[' )
140+ {
141+ PushTurtleState (turtle );
142+ }
143+ else if ( step == ']' )
144+ {
145+ turtle = PopTurtleState ();
146+ }
147+ else if ( ( step >= 48 ) && ( step <= 57 ) )
148+ {
149+ repeats = (int ) step - 48 ;
150+ }
151+ }
152+
153+ turtleTop = -1 ;
154+
155+ }
156+
157+ void BuildProductionStep (PenroseLSystem * ls )
158+ {
159+ char * newProduction = (char * ) malloc (sizeof (char ) * STR_MAX_SIZE );
160+ newProduction [0 ] = '\0' ;
161+
162+ int productionLength = strnlen (ls -> production , STR_MAX_SIZE );
163+
164+ for (int i = 0 ; i < productionLength ; i ++ )
165+ {
166+ char step = ls -> production [i ];
167+ int remainingSpace = STR_MAX_SIZE - strnlen (newProduction , STR_MAX_SIZE ) - 1 ;
168+ switch (step )
169+ {
170+ case 'W' : strncat (newProduction , ls -> ruleW , remainingSpace ); break ;
171+ case 'X' : strncat (newProduction , ls -> ruleX , remainingSpace ); break ;
172+ case 'Y' : strncat (newProduction , ls -> ruleY , remainingSpace ); break ;
173+ case 'Z' : strncat (newProduction , ls -> ruleZ , remainingSpace ); break ;
174+ default :
175+ {
176+ if (step != 'F' )
177+ {
178+ int t = strnlen (newProduction , STR_MAX_SIZE );
179+ newProduction [t ] = step ;
180+ newProduction [t + 1 ] = '\0' ;
181+ }
182+ } break ;
183+ }
184+ }
185+
186+ ls -> drawLength *= 0.5f ;
187+ strncpy (ls -> production , newProduction , STR_MAX_SIZE );
188+ free ( newProduction );
189+ }
190+
191+ void BuildPenroseLSystem (PenroseLSystem * ls , float drawLength , int generations )
192+ {
193+ * ls = CreatePenroseLSystem (drawLength );
194+ for (int i = 0 ; i < generations ; i ++ )
195+ {
196+ BuildProductionStep (ls );
197+ }
198+ }
199+
200+ //------------------------------------------------------------------------------------
201+ // Program main entry point
202+ //------------------------------------------------------------------------------------
203+ int main (void )
204+ {
205+ // Initialization
206+ //--------------------------------------------------------------------------------------
207+ const int screenWidth = 800 ;
208+ const int screenHeight = 450 ;
209+
210+ SetConfigFlags ( FLAG_MSAA_4X_HINT );
211+ InitWindow (screenWidth , screenHeight , "raylib [shapes] example - penrose tile" );
212+
213+ float drawLength = 460.0f ;
214+ int minGenerations = 0 ;
215+ int maxGenerations = 4 ;
216+ int generations = 0 ;
217+
218+ PenroseLSystem ls = {0 };
219+ BuildPenroseLSystem (& ls , drawLength * (generations / (float ) maxGenerations ), generations );
220+
221+ SetTargetFPS (60 ); // Set our game to run at 60 frames-per-second
222+ //---------------------------------------------------------------------------------------
223+
224+ // Main game loop
225+ while (!WindowShouldClose ()) // Detect window close button or ESC key
226+ {
227+ // Update
228+ //----------------------------------------------------------------------------------
229+ bool rebuild = false;
230+ if (IsKeyPressed (KEY_UP ))
231+ {
232+ if (generations < maxGenerations )
233+ {
234+ generations ++ ;
235+ rebuild = true;
236+ }
237+ }
238+ else if (IsKeyPressed (KEY_DOWN ))
239+ {
240+ if (generations > minGenerations )
241+ {
242+ generations -- ;
243+ rebuild = generations > 0 ;
244+ }
245+ }
246+ if (rebuild )
247+ {
248+ BuildPenroseLSystem (& ls , drawLength * (generations / (float ) maxGenerations ), generations );
249+ }
250+ //----------------------------------------------------------------------------------
251+
252+ // Draw
253+ //----------------------------------------------------------------------------------
254+ BeginDrawing ();
255+ ClearBackground ( RAYWHITE );
256+ if (generations > 0 )
257+ {
258+ DrawPenroseLSystem (& ls );
259+ }
260+ DrawText ("penrose l-system" , 10 , 10 , 20 , DARKGRAY );
261+ DrawText ("press up or down to change generations" , 10 , 30 , 20 , DARKGRAY );
262+ DrawText (TextFormat ("generations: %d" , generations ), 10 , 50 , 20 , DARKGRAY );
263+ EndDrawing ();
264+ //----------------------------------------------------------------------------------
265+ }
266+
267+ // De-Initialization
268+ //--------------------------------------------------------------------------------------
269+ CloseWindow (); // Close window and OpenGL context
270+ //--------------------------------------------------------------------------------------
271+
272+ return 0 ;
273+ }
0 commit comments