/******************************************************************************************* * * raylib [shapes] example - penrose tile * * Example complexity rating: [★★★★] 4/4 * * Example originally created with raylib 5.5 * Based on: https://processing.org/examples/penrosetile.html * * Example contributed by David Buzatto (@davidbuzatto) and reviewed by Ramon Santamaria (@raysan5) * * Example licensed under an unmodified zlib/libpng license, which is an OSI-certified, * BSD-like license that allows static linking with closed source software * * Copyright (c) 2025 David Buzatto (@davidbuzatto) * ********************************************************************************************/ #include "raylib.h" #include #include #include #define STR_MAX_SIZE 10000 #define TURTLE_STACK_MAX_SIZE 50 //---------------------------------------------------------------------------------- // Types and Structures Definition //---------------------------------------------------------------------------------- typedef struct TurtleState { Vector2 origin; double angle; } TurtleState; typedef struct PenroseLSystem { int steps; char *production; const char *ruleW; const char *ruleX; const char *ruleY; const char *ruleZ; float drawLength; float theta; } PenroseLSystem; //---------------------------------------------------------------------------------- // Global Variables Definition //---------------------------------------------------------------------------------- static TurtleState turtleStack[TURTLE_STACK_MAX_SIZE]; static int turtleTop = -1; //---------------------------------------------------------------------------------- // Module Functions Declaration //---------------------------------------------------------------------------------- static void PushTurtleState(TurtleState state); static TurtleState PopTurtleState(void); static PenroseLSystem CreatePenroseLSystem(float drawLength); static void BuildProductionStep(PenroseLSystem *ls); static void DrawPenroseLSystem(PenroseLSystem *ls); //------------------------------------------------------------------------------------ // Program main entry point //------------------------------------------------------------------------------------ int main(void) { // Initialization //-------------------------------------------------------------------------------------- const int screenWidth = 800; const int screenHeight = 450; SetConfigFlags(FLAG_MSAA_4X_HINT); InitWindow(screenWidth, screenHeight, "raylib [shapes] example - penrose tile"); float drawLength = 460.0f; int minGenerations = 0; int maxGenerations = 4; int generations = 0; // Initializee new penrose tile PenroseLSystem ls = CreatePenroseLSystem(drawLength*(generations/(float)maxGenerations)); for (int i = 0; i < generations; i++) BuildProductionStep(&ls); SetTargetFPS(120); // Set our game to run at 120 frames-per-second //--------------------------------------------------------------------------------------- // Main game loop while (!WindowShouldClose()) // Detect window close button or ESC key { // Update //---------------------------------------------------------------------------------- bool rebuild = false; if (IsKeyPressed(KEY_UP)) { if (generations < maxGenerations) { generations++; rebuild = true; } } else if (IsKeyPressed(KEY_DOWN)) { if (generations > minGenerations) { generations--; if (generations > 0) rebuild = true; } } if (rebuild) { RL_FREE(ls.production); // Free previous production for re-creation ls = CreatePenroseLSystem(drawLength*(generations/(float)maxGenerations)); for (int i = 0; i < generations; i++) BuildProductionStep(&ls); } //---------------------------------------------------------------------------------- // Draw //---------------------------------------------------------------------------------- BeginDrawing(); ClearBackground( RAYWHITE ); if (generations > 0) DrawPenroseLSystem(&ls); DrawText("penrose l-system", 10, 10, 20, DARKGRAY); DrawText("press up or down to change generations", 10, 30, 20, DARKGRAY); DrawText(TextFormat("generations: %d", generations), 10, 50, 20, DARKGRAY); EndDrawing(); //---------------------------------------------------------------------------------- } // De-Initialization //-------------------------------------------------------------------------------------- CloseWindow(); // Close window and OpenGL context //-------------------------------------------------------------------------------------- return 0; } //---------------------------------------------------------------------------------- // Module Functions Definition //---------------------------------------------------------------------------------- // Push turtle state for next step static void PushTurtleState(TurtleState state) { if (turtleTop < (TURTLE_STACK_MAX_SIZE - 1)) turtleStack[++turtleTop] = state; else TraceLog(LOG_WARNING, "TURTLE STACK OVERFLOW!"); } // Pop turtle state step static TurtleState PopTurtleState(void) { if (turtleTop >= 0) return turtleStack[turtleTop--]; else TraceLog(LOG_WARNING, "TURTLE STACK UNDERFLOW!"); return (TurtleState){ 0 }; } // Create a new penrose tile structure static PenroseLSystem CreatePenroseLSystem(float drawLength) { // TODO: Review constant values assignment on recreation? PenroseLSystem ls = { .steps = 0, .ruleW = "YF++ZF4-XF[-YF4-WF]++", .ruleX = "+YF--ZF[3-WF--XF]+", .ruleY = "-WF++XF[+++YF++ZF]-", .ruleZ = "--YF++++WF[+ZF++++XF]--XF", .drawLength = drawLength, .theta = 36.0f // Degrees }; ls.production = (char *)RL_MALLOC(sizeof(char)*STR_MAX_SIZE); ls.production[0] = '\0'; strncpy(ls.production, "[X]++[X]++[X]++[X]++[X]", STR_MAX_SIZE); return ls; } // Build next penrose step static void BuildProductionStep(PenroseLSystem *ls) { char *newProduction = (char *)RL_MALLOC(sizeof(char)*STR_MAX_SIZE); newProduction[0] = '\0'; int productionLength = strnlen(ls->production, STR_MAX_SIZE); for (int i = 0; i < productionLength; i++) { char step = ls->production[i]; int remainingSpace = STR_MAX_SIZE - strnlen(newProduction, STR_MAX_SIZE) - 1; switch (step) { case 'W': strncat(newProduction, ls->ruleW, remainingSpace); break; case 'X': strncat(newProduction, ls->ruleX, remainingSpace); break; case 'Y': strncat(newProduction, ls->ruleY, remainingSpace); break; case 'Z': strncat(newProduction, ls->ruleZ, remainingSpace); break; default: { if (step != 'F') { int t = strnlen(newProduction, STR_MAX_SIZE); newProduction[t] = step; newProduction[t + 1] = '\0'; } } break; } } ls->drawLength *= 0.5f; strncpy(ls->production, newProduction, STR_MAX_SIZE); RL_FREE(newProduction); } // Draw penrose tile lines static void DrawPenroseLSystem(PenroseLSystem *ls) { Vector2 screenCenter = { GetScreenWidth()/2, GetScreenHeight()/2 }; TurtleState turtle = { .origin = { 0 }, .angle = -90.0f }; int repeats = 1; int productionLength = (int)strnlen(ls->production, STR_MAX_SIZE); ls->steps += 12; if (ls->steps > productionLength) ls->steps = productionLength; for (int i = 0; i < ls->steps; i++) { char step = ls->production[i]; if (step == 'F') { for (int j = 0; j < repeats; j++) { Vector2 startPosWorld = turtle.origin; float radAngle = DEG2RAD*turtle.angle; turtle.origin.x += ls->drawLength*cosf(radAngle); turtle.origin.y += ls->drawLength*sinf(radAngle); Vector2 startPosScreen = { startPosWorld.x + screenCenter.x, startPosWorld.y + screenCenter.y }; Vector2 endPosScreen = { turtle.origin.x + screenCenter.x, turtle.origin.y + screenCenter.y }; DrawLineEx(startPosScreen, endPosScreen, 2, Fade(BLACK, 0.2)); } repeats = 1; } else if (step == '+') { for (int j = 0; j < repeats; j++) turtle.angle += ls->theta; repeats = 1; } else if (step == '-') { for (int j = 0; j < repeats; j++) turtle.angle += -ls->theta; repeats = 1; } else if (step == '[') PushTurtleState(turtle); else if (step == ']') turtle = PopTurtleState(); else if ((step >= 48) && (step <= 57)) repeats = (int) step - 48; } turtleTop = -1; }