penrose tile
There is no browser build of this example yet.
Is it the same as C?
- Desktop:verified The Zig version renders a final frame that is byte-identical to the C example.
- Browser:verified In headless Chrome, the Zig version gives a final frame byte-identical to the C example built for the web.
//! 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.