← ziglang.inraylib-zigGitHub

penrose tile

★★★★by David Buzatto (@davidbuzatto)

There is no browser build of this example yet.

Is it the same as C?

//! raylib [shapes] example - penrose tile
//!
//! Zig-API translation of examples/shapes/shapes_penrose_tile.c (raylib 6.2).
//! Example licensed under an unmodified zlib/libpng license.
//! Copyright (c) 2025 David Buzatto (@davidbuzatto)

const std = @import("std");
const rl = @import("raylib");

const str_max_size = 10000;
const turtle_stack_max_size = 50;

const TurtleState = struct {
    origin: rl.Vector2 = .{ .x = 0, .y = 0 },
    angle: f32 = 0,
};

const TurtleStack = struct {
    items: [turtle_stack_max_size]TurtleState = undefined,
    len: usize = 0,

    /// Push turtle state for next step
    fn push(stack: *TurtleStack, state: TurtleState) void {
        if (stack.len < turtle_stack_max_size) {
            stack.items[stack.len] = state;
            stack.len += 1;
        } else rl.traceLog(.warning, "TURTLE STACK OVERFLOW!", .{});
    }

    /// Pop turtle state step
    fn pop(stack: *TurtleStack) TurtleState {
        if (stack.len == 0) {
            rl.traceLog(.warning, "TURTLE STACK UNDERFLOW!", .{});
            return .{};
        }
        stack.len -= 1;
        return stack.items[stack.len];
    }
};

/// A production string, truncated (like the C `strncat`) to `str_max_size - 1` characters.
const Production = struct {
    buffer: [str_max_size - 1]u8 = undefined,
    len: usize = 0,

    fn init(text: []const u8) Production {
        var production: Production = .{};
        production.append(text);
        return production;
    }

    fn append(production: *Production, text: []const u8) void {
        const n = @min(text.len, production.buffer.len - production.len);
        @memcpy(production.buffer[production.len..][0..n], text[0..n]);
        production.len += n;
    }

    fn slice(production: *const Production) []const u8 {
        return production.buffer[0..production.len];
    }
};

const PenroseLSystem = struct {
    steps: usize = 0,
    production: Production,
    draw_length: f32,
    theta: f32 = 36.0, // Degrees

    const rule_w = "YF++ZF4-XF[-YF4-WF]++";
    const rule_x = "+YF--ZF[3-WF--XF]+";
    const rule_y = "-WF++XF[+++YF++ZF]-";
    const rule_z = "--YF++++WF[+ZF++++XF]--XF";

    /// Create a new penrose tile structure, built up to `generations`
    fn init(draw_length: f32, generations: u32) PenroseLSystem {
        var ls: PenroseLSystem = .{
            .production = .init("[X]++[X]++[X]++[X]++[X]"),
            .draw_length = draw_length,
        };
        for (0..generations) |_| ls.buildProductionStep();
        return ls;
    }

    /// Build next penrose step
    fn buildProductionStep(ls: *PenroseLSystem) void {
        var new_production: Production = .{};

        for (ls.production.slice()) |step| {
            switch (step) {
                'W' => new_production.append(rule_w),
                'X' => new_production.append(rule_x),
                'Y' => new_production.append(rule_y),
                'Z' => new_production.append(rule_z),
                'F' => {},
                else => new_production.append(&.{step}),
            }
        }

        ls.draw_length *= 0.5;
        ls.production = new_production;
    }

    /// Draw penrose tile lines
    fn draw(ls: *PenroseLSystem) void {
        const screen_center: rl.Vector2 = .{
            .x = @as(f32, @floatFromInt(rl.getScreenWidth())) / 2.0,
            .y = @as(f32, @floatFromInt(rl.getScreenHeight())) / 2.0,
        };

        var turtle: TurtleState = .{ .angle = -90.0 };
        var stack: TurtleStack = .{};

        var repeats: u32 = 1;
        ls.steps = @min(ls.steps + 12, ls.production.len);

        for (ls.production.slice()[0..ls.steps]) |step| {
            switch (step) {
                'F' => {
                    for (0..repeats) |_| {
                        const start_pos_world = turtle.origin;
                        const rad_angle = rl.deg2rad * turtle.angle;
                        turtle.origin.x += ls.draw_length * @cos(rad_angle);
                        turtle.origin.y += ls.draw_length * @sin(rad_angle);
                        const start_pos_screen: rl.Vector2 = .{ .x = start_pos_world.x + screen_center.x, .y = start_pos_world.y + screen_center.y };
                        const end_pos_screen: rl.Vector2 = .{ .x = turtle.origin.x + screen_center.x, .y = turtle.origin.y + screen_center.y };

                        rl.drawLineEx(start_pos_screen, end_pos_screen, 2, rl.fade(.black, 0.2));
                    }
                    repeats = 1;
                },
                '+' => {
                    for (0..repeats) |_| turtle.angle += ls.theta;
                    repeats = 1;
                },
                '-' => {
                    for (0..repeats) |_| turtle.angle += -ls.theta;
                    repeats = 1;
                },
                '[' => stack.push(turtle),
                ']' => turtle = stack.pop(),
                '0'...'9' => repeats = step - '0',
                else => {},
            }
        }
    }
};

pub fn main() void {
    // Initialization
    const screen_width = 800;
    const screen_height = 450;

    rl.setConfigFlags(.{ .msaa_4x_hint = true });
    rl.initWindow(screen_width, screen_height, "raylib [shapes] example - penrose tile");
    defer rl.closeWindow(); // Close window and OpenGL context

    const draw_length: f32 = 460.0;
    const min_generations = 0;
    const max_generations = 4;
    var generations: u32 = 0;

    // Initialize new penrose tile
    var ls: PenroseLSystem = .init(draw_length * (@as(f32, @floatFromInt(generations)) / max_generations), generations);

    rl.setTargetFPS(120); // Set our game to run at 120 frames-per-second

    // Main game loop
    while (!rl.windowShouldClose()) { // Detect window close button or ESC key
        // Update
        var rebuild = false;
        if (rl.isKeyPressed(.up)) {
            if (generations < max_generations) {
                generations += 1;
                rebuild = true;
            }
        } else if (rl.isKeyPressed(.down)) {
            if (generations > min_generations) {
                generations -= 1;
                if (generations > 0) rebuild = true;
            }
        }

        if (rebuild) {
            ls = .init(draw_length * (@as(f32, @floatFromInt(generations)) / max_generations), generations);
        }

        // Draw
        rl.beginDrawing();
        defer rl.endDrawing();

        rl.clearBackground(.ray_white);

        if (generations > 0) ls.draw();

        rl.drawText("penrose l-system", 10, 10, 20, .dark_gray);
        rl.drawText("press up or down to change generations", 10, 30, 20, .dark_gray);
        rl.drawText(rl.textFormat("generations: {d}", .{generations}), 10, 50, 20, .dark_gray);
    }
}
/*******************************************************************************************
*
*   raylib [shapes] example - penrose tile
*
*   Example complexity rating: [★★★★] 4/4
*
*   Example originally created with raylib 5.5, last time updated with raylib 6.0
*   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 "raylib.h"

#include <stdlib.h>
#include <string.h>
#include <math.h>

#define STR_MAX_SIZE            10000
#define TURTLE_STACK_MAX_SIZE      50

//----------------------------------------------------------------------------------
// Types and Structures Definition
//----------------------------------------------------------------------------------
typedef struct TurtleState {
    Vector2 origin;
    float 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;

//----------------------------------------------------------------------------------
// Global Variables Definition
//----------------------------------------------------------------------------------
static TurtleState turtleStack[TURTLE_STACK_MAX_SIZE];
static int turtleTop = -1;

//----------------------------------------------------------------------------------
// Module Functions Declaration
//----------------------------------------------------------------------------------
static void PushTurtleState(TurtleState state);
static TurtleState PopTurtleState(void);
static PenroseLSystem CreatePenroseLSystem(float drawLength);
static void BuildProductionStep(PenroseLSystem *ls);
static void DrawPenroseLSystem(PenroseLSystem *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;

    // Initializee new penrose tile
    PenroseLSystem ls = CreatePenroseLSystem(drawLength*(generations/(float)maxGenerations));
    for (int i = 0; i < generations; i++) BuildProductionStep(&ls);

    SetTargetFPS(120);              // Set our game to run at 120 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--;
                if (generations > 0) rebuild = true;
            }
        }

        if (rebuild)
        {
            RL_FREE(ls.production); // Free previous production for re-creation
            ls = CreatePenroseLSystem(drawLength*(generations/(float)maxGenerations));
            for (int i = 0; i < generations; i++) BuildProductionStep(&ls);
        }
        //----------------------------------------------------------------------------------

        // 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;
}

//----------------------------------------------------------------------------------
// Module Functions Definition
//----------------------------------------------------------------------------------
// Push turtle state for next step
static void PushTurtleState(TurtleState state)
{
    if (turtleTop < (TURTLE_STACK_MAX_SIZE - 1)) turtleStack[++turtleTop] = state;
    else TraceLog(LOG_WARNING, "TURTLE STACK OVERFLOW!");
}

// Pop turtle state step
static TurtleState PopTurtleState(void)
{
    if (turtleTop >= 0) return turtleStack[turtleTop--];
    else TraceLog(LOG_WARNING, "TURTLE STACK UNDERFLOW!");

    return (TurtleState){ 0 };
}

// Create a new penrose tile structure
static PenroseLSystem CreatePenroseLSystem(float drawLength)
{
    // TODO: Review constant values assignment on recreation?
    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 // Degrees
    };

    ls.production = (char *)RL_MALLOC(sizeof(char)*STR_MAX_SIZE);
    ls.production[0] = '\0';
    strncpy(ls.production, "[X]++[X]++[X]++[X]++[X]", STR_MAX_SIZE);

    return ls;
}

// Build next penrose step
static void BuildProductionStep(PenroseLSystem *ls)
{
    char *newProduction = (char *)RL_MALLOC(sizeof(char)*STR_MAX_SIZE);
    newProduction[0] = '\0';

    int productionLength = (int)strnlen(ls->production, STR_MAX_SIZE);

    for (int i = 0; i < productionLength; i++)
    {
        char step = ls->production[i];
        int remainingSpace = STR_MAX_SIZE - (int)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 = (int)strnlen(newProduction, STR_MAX_SIZE);
                    newProduction[t] = step;
                    newProduction[t + 1] = '\0';
                }
            } break;
        }
    }

    ls->drawLength *= 0.5f;
    strncpy(ls->production, newProduction, STR_MAX_SIZE);

    RL_FREE(newProduction);
}

// Draw penrose tile lines
static void DrawPenroseLSystem(PenroseLSystem *ls)
{
    Vector2 screenCenter = { GetScreenWidth()/2.0f, GetScreenHeight()/2.0f };

    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.2f));
            }

            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;
}

From raylib 6.2. The original on GitHub.