Penrose Tilings

Larry Bartholdi & his gang

July 24, 2024

My warmest thanks to Shalom Eliahou, who suggested programming Penrose tilings in Scheme.

We will devise recursive procedures to draw Penrose tilings using PCSCHEME, and displaying the results through BORLAND's BGI interface.

Penrose's genius was to discover two triangles, that can each be cut in two parts such that each part is a miniature replica of one of the big triangles. The process can be viewed from bottom up (assemble triangles to form bigger ones) or from top down (decompose the triangles until they are small enough). We chose here the latter approach.

First define a few useful constants: τ is the golden ratio, φ is π/5. The large and small triangles have sides 1, τ, τ and 1, 1, τ respectively, with angles that are multiples of φ. |csize| is the size of the circles drawn at the triangles'summits. (define tau (/ (+ (sqrt 5) 1) 2)) (define phi (/ pi 5)) (define csize 3)

This procedure is handy for doing point arithmetic. It adds the vector of orientation |angle| (in multiples of φ) and length |size| to the point |p|. (define (p+ point size angle) (cons (+ (car point) (* size (cos (* angle phi)))) (+ (cdr point) (* size (sin (* angle phi))))))

Here we actually draw a triangle of coordinates |a|, |b| and |c|. The boolean parameters |pa|, |pb| and |pc| indicate whether the summit is hollow (if false) a filled (if true). (define (triangle a b c pa pb pc) (let ((plist (map (lambda (point) (cons (round (car point)) (round (cdr point)))) (list b c a))) (fill (lambda (point) (fill-ellipse point (cons csize csize)))) (outline (lambda (point) (ellipse point 0 360 (cons csize csize))))) (draw-poly (cons (caddr plist) plist)) ((if pb fill outline) (car plist)) ((if pc fill outline) (cadr plist)) ((if pa fill outline) (caddr plist))))

This is the actual recursive code. It will draw a large triangle at position pair |position|, with lengths |size| or τ| size|, rotated φ| angle|. |Orientation| indicates whether the colouring scheme is standard or reversed, and |level| is the recursion level, of course. (define (l position size angle orientation level) (let ((size/tau (/ size tau)) (size*tau (* size tau)) (dol (lambda (ds da ns na no) (l (p+ position ds (+ angle da)) ns (+ angle na) (eq? no orientation) (- level 1)))) (dos (lambda (ds da ns na no) (s (p+ position ds (+ angle da)) ns (+ angle na) (eq? no orientation) (- level 1)))))

(if (= level 0) (triangle position (p+ position (* size tau) (+ angle 2)) (p+ position size angle) (not orientation) #t orientation) (if (odd? level) (if orientation (begin (dol size 1 size/tau 7 #f) (dos size 1 size/tau 6 #t)) (begin (dol size 0 size/tau 3 #f) (dos size*tau 0 size/tau 4 #t))) (if orientation (begin (dol size*tau 2 size 7 #t) (dos size 0 size/tau 3 #t)) (begin (dol size 0 size 3 #t) (dos size/tau 2 size/tau 7 #t)))))))

(define (s position size angle orientation level) (if (= level 0) (triangle position (p+ position size (+ angle 1)) (p+ position (* size tau) angle) orientation #f (not orientation)) (l position size angle orientation (- level 1))))

Finally a few shorthands. (define (i) (init-graph) (split-screen 10)) (define c close-graph) (define d clear-device)