Browse Source

[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
David Buzatto 2 weeks ago
parent
commit
3ba186f2c1
2 changed files with 273 additions and 0 deletions
  1. 273 0
      examples/shapes/shapes_penrose_tile.c
  2. BIN
      examples/shapes/shapes_penrose_tile.png

+ 273 - 0
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 <stdlib.h>
+#include <string.h>
+#include <math.h>
+#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;
+}

BIN
examples/shapes/shapes_penrose_tile.png