From 84f1e5dd1c770288935f6b25cc93f7055401e5ed Mon Sep 17 00:00:00 2001 From: David Buzatto Date: Sat, 29 Nov 2025 21:24:42 -0300 Subject: [PATCH] new shapes example - penrose tile --- examples/shapes/shapes_penrose_tile.c | 270 ++++++++++++++++++++++++ examples/shapes/shapes_penrose_tile.png | Bin 0 -> 14618 bytes 2 files changed, 270 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..8883f0eb5 --- /dev/null +++ b/examples/shapes/shapes_penrose_tile.c @@ -0,0 +1,270 @@ +/******************************************************************************************* +* +* 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 2500 + +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'; + strcpy(ls.production, "[X]++[X]++[X]++[X]++[X]"); + 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) strlen(ls->production); + 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; + } + } +} + +void BuildProductionStep(PenroseLSystem *ls) +{ + char *newProduction = (char*) malloc(sizeof(char) * STR_MAX_SIZE); + newProduction[0] = '\0'; + + int productionLength = strlen(ls->production); + + for (int i = 0; i < productionLength; i++) + { + char step = ls->production[i]; + switch (step) + { + case 'W': strcat(newProduction, ls->ruleW); break; + case 'X': strcat(newProduction, ls->ruleX); break; + case 'Y': strcat(newProduction, ls->ruleY); break; + case 'Z': strcat(newProduction, ls->ruleZ); break; + default: + { + if (step != 'F') + { + int t = strlen(newProduction); + newProduction[t] = step; + newProduction[t+1] = '\0'; + } + } break; + } + } + + ls->drawLength *= 0.5f; + strcpy( ls->production, newProduction ); + free( newProduction ); +} + +void BuildPenroseLSystem(PenroseLSystem *ls, float drawLength, int generations) +{ + *ls = CreatePenroseLSystem(drawLength); + turtleTop = -1; + 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..98c7131fe57f5bb8462a4f941eec7c1c9a6c52f6 GIT binary patch literal 14618 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 Vtk-dL6Obx&