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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 literal 0 HcmV?d00001