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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 sVzx0*H