# KeiruaProd

I help my clients acquire new users and make more money with their web businesses. I have ten years of experience with SaaS projects. If that’s something you need help with, we should get in touch!
< Back to article list

## Penrose tiling

I wrote a thing forever ago that generates SVG images of the Penrose tiling.

## Animating the tiling

Note to self: in order to generate this animation, I’ve generated the output for every step in svg using said tool, then I’ve converted the `svg` output to `png` using Imagick:

``````convert output/svg/penrose1.svg output/png/penrose1.png
``````

Then I used ffmpeg to generate the gif:

``````ffmpeg -framerate 1 -i "output/png/penrose%d.png" output/animation.gif
``````

## The Penrose Tiling

Starting from 10 red triangles distributed along a circle, each triangle is subdivided.

There are 2 kinds of triangles, red and blue, and each are subdivided differently, always creating new red or blue triangles with the same proportions:

## Generating the tiling

``````IMG_WIDTH = 1000
IMG_HEIGHT = 1000

if ARGV.count < 3
depth = 5
nb_triangles = 10
start_color = :red
else
depth = ARGV[0].to_i
nb_triangles = ARGV[1].to_i
start_color = ARGV[2].to_sym
end

GOLDEN_RATIO = (1 + Math.sqrt(5)) / 2.0

class Point
attr_accessor :x, :y

def initialize(x = 0, y = 0)
@x = x
@y = y
end

def self.transform(p)
Point.new((IMG_WIDTH/2 + p.x * 0.8 * IMG_WIDTH/2).to_i, (IMG_HEIGHT/2 + p.y * 0.8 * IMG_HEIGHT/2).to_i)
end

def self.lerp(a, b, alpha)
Point.new(a.x + (b.x - a.x)*alpha, a.y + (b.y - a.y)*alpha)
end
end

class Triangle
attr_accessor :a, :b, :c
attr_accessor :color

def initialize(a, b, c, color)
@a = a
@b = b
@c = c
@color = color
end

def subdivide()
result = []
if color == :red
p = Point.lerp(a, b, 1/GOLDEN_RATIO)
result << [Triangle.new(c, p, b, :red), Triangle.new(p, c, a, :blue)]
else
q = Point.lerp(b, a, 1/GOLDEN_RATIO)
r = Point.lerp(b, c, 1/GOLDEN_RATIO)
result << [Triangle.new(r, c, a, :blue), Triangle.new(q, r, b, :blue), Triangle.new(r, q, a, :red)]
end
return result
end
end

initial_triangles = []
nb_triangles.times do |i|
b_complex = Complex.polar(1, ((2*i - 1)*Math::PI/10))
c_complex = Complex.polar(1, ((2*i + 1)*Math::PI/10))

b_complex, c_complex = c_complex, b_complex if i.even?

b_point = Point.new(b_complex.real, b_complex.imaginary)
c_point = Point.new(c_complex.real, c_complex.imaginary)

initial_triangles << Triangle.new(Point.new, b_point, c_point, start_color)
end

triangles = initial_triangles

(depth-1).times do
triangles = triangles.map { |t| t.subdivide }.flatten
end

def line(a, b, stroke_color)
"<line x1=\"#{a.x}\" y1=\"#{a.y}\" x2=\"#{b.x}\" y2=\"#{b.y}\" stroke=\"#{stroke_color}\" />\n"
end

def triangle(a, b, c, fill_color)
"<polygon points=\"#{a.x},#{a.y} #{b.x},#{b.y} #{c.x},#{c.y}\" fill=\"#{fill_color}\" />\n"
end

red_fill_color = '#FF6060'
blue_fill_color = '#6060FF'
stroke_color = '#404040'
img = "<svg viewBox=\"0 0 #{IMG_WIDTH} #{IMG_HEIGHT}\" xmlns=\"http://www.w3.org/2000/svg\">\n"

triangles.each do |t|
a = Point.transform(t.a)
b = Point.transform(t.b)
c = Point.transform(t.c)

img << triangle(a, b, c, t.color == :red ? red_fill_color : blue_fill_color)
img << line(a, b, stroke_color)
img << line(c, a, stroke_color)
end

img << "</svg>"
puts img
``````

## Usage

You can provide the depth (1 to 10, default 5), the number of subdivisions of the circle (10 for penrose), and the start color (red or blue, default red)

``````\$ ruby main.rb > penrose-depth-5.svg
\$ ruby main.rb 3 10 blue > test.svg
``````

Here is the output at depth 4: