Aside: I wonder if anyone has tried to make an exhaustive list of Penrose tiles in architecture. I knew the San Francisco transit center has a Penrose tile design. Now I see Pomona College (southern California) has a rhomb tiling in their physics/mathematics/astronomy building:
https://searockstaffordcm.com/pomona-college/
I updated my code to assign more colors to tiles using the last three deflation rules applied. It picks a color randomly from a palette (hardcoded for kites and darts but you can easily change it) the first time it sees the rules and reuses it for others.
Code: Select all
import sys
import math
import numpy
import random
ROTATION = 36 * math.pi / 180
RATIO = 2/ (math.sqrt(5) + 1)
FLIP_Y = numpy.matrix([[1, 0, 0], [0, -1, 0], [0, 0, 1]])
ROTATE = numpy.matrix([
[math.cos(ROTATION), -math.sin(ROTATION), 0],
[math.sin(ROTATION), math.cos(ROTATION), 0],
[0, 0, 1]])
SCALE = numpy.matrix([[RATIO, 0, 0], [0, RATIO, 0], [0, 0, 1]])
# To give tiling a layered appearance (distance is relative to side length)
SHIFT_DISTANCE = 0.12
SHIFT_ANGLE = 43 * math.pi / 180
def translate(x, y):
return numpy.matrix([[1, 0, x], [0, 1, y], [0, 0, 1]])
def to_components(transformation):
xmult = transformation[0, 0]
ymult = transformation[1, 0]
theta = int(round(math.atan2(ymult, xmult) * 180 / math.pi))
scale = math.sqrt(xmult * xmult + ymult * ymult)
return(transformation[0, 2], transformation[1, 2], theta, scale)
HALF_KITE_TO_HALF_KITE_1 = translate(1, 0) * ROTATE ** 7 * SCALE
HALF_KITE_TO_HALF_KITE_2 = translate(1, 0) * ROTATE ** 5 * FLIP_Y * SCALE
HALF_KITE_TO_HALF_DART = ROTATE ** 9 * FLIP_Y * SCALE
HALF_DART_TO_HALF_KITE = SCALE
HALF_DART_TO_HALF_DART = translate(1, 0) * ROTATE ** 6 * SCALE
SVG_HEAD = '''<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
width="420mm"
height="297mm"
viewBox="0 0 420 297"
version="1.1"
id="svg8">
<g id="layer1" transform="scale(250)">'''
SVG_TAIL = '''</g></svg>'''
start_tile = sys.argv[1]
depth = int(sys.argv[2])
# set up starting tiles
rotation = numpy.identity(3)
tiles = []
for i in range(5):
tiles.append((start_tile, rotation, '%d+_' % i))
tiles.append((start_tile, FLIP_Y * rotation, '%d-_' % i))
rotation = rotation * ROTATE * ROTATE
# apply deflation rules
for rep in range(depth):
new_tiles = []
for kind, transformation, rules in tiles:
if kind == 'kite':
new_tiles.append(('kite', transformation * HALF_KITE_TO_HALF_KITE_1, rules + 'k1'))
new_tiles.append(('kite', transformation * HALF_KITE_TO_HALF_KITE_2, rules + 'k2'))
new_tiles.append(('dart', transformation * HALF_KITE_TO_HALF_DART, rules + 'k3'))
else:
new_tiles.append(('kite', transformation * HALF_DART_TO_HALF_KITE, rules + 'd1'))
new_tiles.append(('dart', transformation * HALF_DART_TO_HALF_DART, rules + 'd2'))
tiles = new_tiles
# eliminate flipped tile halves so we draw only whole non-flipped tiles
not_flipped = [tile for tile in tiles if numpy.linalg.det(tile[1]) > 0]
kites = [tile for tile in not_flipped if tile[0] == 'kite']
darts = [tile for tile in not_flipped if tile[0] == 'dart']
print(SVG_HEAD)
# darts in same position as kites for shadows; no stroke
print(' <g style="fill:#384657;fill-opacity:1">')
for name, mat, rules in darts:
print(''' <path transform="translate(%f,%f) rotate(%d) scale(%f)"
d="M 0,0 1,0 0.5,-0.36 0.31,-0.95 z"
id="dart_bg_%s"/>''' % (to_components(mat) + (rules,)))
print(' </g>')
dart_colors = ['4b5d75', '526680', '596e8a', '607795', '69809e']
kite_colors = ['ff7f77', 'f7d0ae', 'e4e2af', 'd2d2d2', 'eeceb8', 'ade2e5', 'ebe0a9', '44feff', 'dfd0c5', 'fa85f5']
palette = {}
# darts in shifted positions
shift = SHIFT_DISTANCE * RATIO ** depth
print(''' <g transform="translate(%f,%f)">''' % (shift * math.cos(SHIFT_ANGLE), shift * math.sin(SHIFT_ANGLE)))
for name, mat, rules in darts:
print(''' <path transform="translate(%f,%f) rotate(%d) scale(%f)"
style="fill:#%s;fill-opacity:1;stroke:#909090;stroke-width:0.02;stroke-linejoin:miter;stroke-opacity:1"
d="M 0,0 1,0 0.5,-0.36 0.31,-0.95 z"
id="dart_%s"/>''' % (to_components(mat) + (palette.setdefault(rules[-6:], random.choice(dart_colors)), rules,)))
print(' </g>')
# kites
print(' <g>')
for name, mat, rules in kites:
print(''' <path transform="translate(%f,%f) rotate(%d) scale(%f)"
style="fill:#%s;fill-opacity:1;stroke:#909090;stroke-width:0.02;stroke-linejoin:miter;stroke-opacity:1"
d="M 0,0 1,0 0.81,-0.59 0.31,-0.95 z"
id="kite_%s"/>''' % (to_components(mat) + (palette.setdefault(rules[-6:], random.choice(kite_colors)), rules,)))
print(' </g>')
print(SVG_TAIL)
Here's an example.
- layer1a.png (1.11 MiB) Viewed 2099 times
With a less garish palette:
Code: Select all
dart_colors = ['526680', '596e8a', '607795', '69809e']
kite_colors = ['84bbcf', '8dc0d3', '97c5d7', 'a0cbda', 'a9d0de', 'b3d5e1', 'bcdae5', 'c6dfe9', 'cfe5ec', 'd9eaf0']
- layer1.png (1.08 MiB) Viewed 2098 times
Update: This is more visually interesting in my opinion. I can assign the colors based on rotation angle (I knew there was some reason to pull those out of the transformation) and use it to create a very primitive diffuse lighting effect as if the tilings are tilted towards their shared vertices. Updated code and example. (
Note: I first posted this with the wrong color palette and I have now fixed this.)
Code: Select all
import sys
import math
import numpy
import random
ROTATION = 36 * math.pi / 180
RATIO = 2/ (math.sqrt(5) + 1)
FLIP_Y = numpy.matrix([[1, 0, 0], [0, -1, 0], [0, 0, 1]])
ROTATE = numpy.matrix([
[math.cos(ROTATION), -math.sin(ROTATION), 0],
[math.sin(ROTATION), math.cos(ROTATION), 0],
[0, 0, 1]])
SCALE = numpy.matrix([[RATIO, 0, 0], [0, RATIO, 0], [0, 0, 1]])
# To give tiling a layered appearance (distance is relative to side length)
SHIFT_DISTANCE = 0.12
SHIFT_ANGLE = 43 * math.pi / 180
def translate(x, y):
return numpy.matrix([[1, 0, x], [0, 1, y], [0, 0, 1]])
def to_components(transformation):
xmult = transformation[0, 0]
ymult = transformation[1, 0]
theta = int(round(math.atan2(ymult, xmult) * 180 / math.pi))
scale = math.sqrt(xmult * xmult + ymult * ymult)
return(transformation[0, 2], transformation[1, 2], theta, scale)
HALF_KITE_TO_HALF_KITE_1 = translate(1, 0) * ROTATE ** 7 * SCALE
HALF_KITE_TO_HALF_KITE_2 = translate(1, 0) * ROTATE ** 5 * FLIP_Y * SCALE
HALF_KITE_TO_HALF_DART = ROTATE ** 9 * FLIP_Y * SCALE
HALF_DART_TO_HALF_KITE = SCALE
HALF_DART_TO_HALF_DART = translate(1, 0) * ROTATE ** 6 * SCALE
SVG_HEAD = '''<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
width="420mm"
height="297mm"
viewBox="0 0 420 297"
version="1.1"
id="svg8">
<g id="layer1" transform="scale(250)">'''
SVG_TAIL = '''</g></svg>'''
start_tile = sys.argv[1]
depth = int(sys.argv[2])
# set up starting tiles
rotation = numpy.identity(3)
tiles = []
for i in range(5):
tiles.append((start_tile, rotation, '%d+_' % i))
tiles.append((start_tile, FLIP_Y * rotation, '%d-_' % i))
rotation = rotation * ROTATE * ROTATE
# apply deflation rules
for rep in range(depth):
new_tiles = []
for kind, transformation, rules in tiles:
if kind == 'kite':
new_tiles.append(('kite', transformation * HALF_KITE_TO_HALF_KITE_1, rules + 'k1'))
new_tiles.append(('kite', transformation * HALF_KITE_TO_HALF_KITE_2, rules + 'k2'))
new_tiles.append(('dart', transformation * HALF_KITE_TO_HALF_DART, rules + 'k3'))
else:
new_tiles.append(('kite', transformation * HALF_DART_TO_HALF_KITE, rules + 'd1'))
new_tiles.append(('dart', transformation * HALF_DART_TO_HALF_DART, rules + 'd2'))
tiles = new_tiles
# eliminate flipped tile halves so we draw only whole non-flipped tiles
not_flipped = [tile for tile in tiles if numpy.linalg.det(tile[1]) > 0]
kites = [tile for tile in not_flipped if tile[0] == 'kite']
darts = [tile for tile in not_flipped if tile[0] == 'dart']
print(SVG_HEAD)
# darts in same position as kites for shadows; no stroke
print(' <g style="fill:#384657;fill-opacity:1">')
for name, mat, rules in darts:
print(''' <path transform="translate(%f,%f) rotate(%d) scale(%f)"
d="M 0,0 1,0 0.5,-0.36 0.31,-0.95 z"
id="dart_bg_%s"/>''' % (to_components(mat) + (rules,)))
print(' </g>')
dart_colors = ['7693b8', '6d87a9', '647c9b', '5b718e', '536680', '4f627a', '576c88', '607795', '6982a3', '718db0']
kite_colors = ['a9d0de', 'afd8e7', 'b7e2f1', 'bfebfb', 'c6f5ff', 'cbfaff', 'c3f0ff', 'bbe7f6', 'b4deec', 'acd4e2']
palette = {}
# darts in shifted positions
shift = SHIFT_DISTANCE * RATIO ** depth
print(''' <g transform="translate(%f,%f)">''' % (shift * math.cos(SHIFT_ANGLE), shift * math.sin(SHIFT_ANGLE)))
for name, mat, rules in darts:
components = to_components(mat)
print(''' <path transform="translate(%f,%f) rotate(%d) scale(%f)"
style="fill:#%s;fill-opacity:1;stroke:#909090;stroke-width:0.02;stroke-linejoin:miter;stroke-opacity:1"
d="M 0,0 1,0 0.5,-0.36 0.31,-0.95 z"
id="dart_%s"/>''' % (components + (dart_colors[(components[2] // 36) % 10], rules,)))
print(' </g>')
# kites
print(' <g>')
for name, mat, rules in kites:
components = to_components(mat)
print(''' <path transform="translate(%f,%f) rotate(%d) scale(%f)"
style="fill:#%s;fill-opacity:1;stroke:#909090;stroke-width:0.02;stroke-linejoin:miter;stroke-opacity:1"
d="M 0,0 1,0 0.81,-0.59 0.31,-0.95 z"
id="kite_%s"/>''' % (components + (kite_colors[(components[2] // 36) % 10], rules,)))
print(' </g>')
print(SVG_TAIL)
The idea is to suggest that the kites form peaks and the darts form valleys.
- layer1.png (1.11 MiB) Viewed 2094 times
One more note: you can eliminate the shift in dart tiles (SHIFT_DISTANCE = 0) and there is still a 3D effect. I think it's a little cleaner this way, but I have hit my attachment limit.