/******************************************************************************************* * * 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 #include #include #include "raylib.h" #define STR_MAX_SIZE 10000 #define TURTLE_STACK_MAX_SIZE 50 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; static TurtleState turtleStack[TURTLE_STACK_MAX_SIZE]; static int turtleTop = -1; void PushTurtleState(TurtleState state) { if (turtleTop < TURTLE_STACK_MAX_SIZE - 1) { turtleStack[++turtleTop] = state; } else { TraceLog(LOG_WARNING, "TURTLE STACK OVERFLOW!"); } } TurtleState PopTurtleState(void) { if (turtleTop >= 0) { return turtleStack[turtleTop--]; } else { TraceLog(LOG_WARNING, "TURTLE STACK UNDERFLOW!"); } return (TurtleState) {0}; } PenroseLSystem CreatePenroseLSystem(float drawLength) { 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 // in degrees }; ls.production = (char*) malloc(sizeof(char) * STR_MAX_SIZE); ls.production[0] = '\0'; strncpy(ls.production, "[X]++[X]++[X]++[X]++[X]", STR_MAX_SIZE); return ls; } 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; } void BuildProductionStep(PenroseLSystem *ls) { char *newProduction = (char*) 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); free( newProduction ); } void BuildPenroseLSystem(PenroseLSystem *ls, float drawLength, int generations) { *ls = CreatePenroseLSystem(drawLength); for (int i = 0; i < generations; i++) { BuildProductionStep(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; PenroseLSystem ls = {0}; BuildPenroseLSystem(&ls, drawLength * (generations / (float) maxGenerations), generations); SetTargetFPS(60); // Set our game to run at 60 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--; rebuild = generations > 0; } } if (rebuild) { BuildPenroseLSystem(&ls, drawLength * (generations / (float) maxGenerations), generations); } //---------------------------------------------------------------------------------- // 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; }