From 3ba186f2c1d6f307740d313653772f0a312f5ec3 Mon Sep 17 00:00:00 2001 From: David Buzatto Date: Mon, 1 Dec 2025 08:57:45 -0300 Subject: [PATCH] [examples] Added: `shapes_penrose_tile` (#5376) * new shapes example - penrose tile * stack cleanup * proper use of strnlen, strncat and strncpy * typo correction * update screenshot of shapes_penrose_tile example --- examples/shapes/shapes_penrose_tile.c | 273 ++++++++++++++++++++++++ examples/shapes/shapes_penrose_tile.png | Bin 0 -> 25548 bytes 2 files changed, 273 insertions(+) create mode 100644 examples/shapes/shapes_penrose_tile.c create mode 100644 examples/shapes/shapes_penrose_tile.png diff --git a/examples/shapes/shapes_penrose_tile.c b/examples/shapes/shapes_penrose_tile.c new file mode 100644 index 000000000..dee62248d --- /dev/null +++ b/examples/shapes/shapes_penrose_tile.c @@ -0,0 +1,273 @@ +/******************************************************************************************* +* +* 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; +} \ No newline at end of file diff --git a/examples/shapes/shapes_penrose_tile.png b/examples/shapes/shapes_penrose_tile.png new file mode 100644 index 0000000000000000000000000000000000000000..dffc35cc4832eaa89dec1aaff25031cde245bd99 GIT binary patch literal 25548 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 P1_lOCS3j3^P6