-
-
Save bazzargh/961b6765042b17c0c25eadcc98b080e6 to your computer and use it in GitHub Desktop.
// this version lets the canvas take care of all the matrix operations. | |
let canvas = document.getElementById('canvas'); | |
let ctx = canvas.getContext('2d'); | |
let A60 = Math.PI/3; | |
let A90 = Math.PI/2; | |
let S3 = Math.sqrt(3); | |
let ANGLES = [0, -2*A60, -A60, 0, 2*A60, A60, 0]; | |
// Centres of patches in H8 at each substitution level. | |
let cache = [ | |
[[0, 0], [0, 0]], [[3, 3*S3], [3, 5*S3]], | |
[[-3, 3*S3], [-6, 4*S3]], [[-6, 0], [-9, -S3]], | |
[[0, -4*S3], [-6, -6*S3]], [[-6, -4*S3], [-15, -7*S3]]] | |
function origin(part, z) { | |
if (part == 6) { | |
return origin(3, z + 1); | |
} | |
if (cache[part].length <= z) { | |
let r1 = origin(part, z - 1); | |
let r2 = origin(part, z - 2); | |
cache[part][z] = [3*r1[0]-r2[0], 3*r1[1]-r2[1]]; | |
} | |
return cache[part][z]; | |
} | |
function monotile(ctx) { | |
let u = 1; | |
let v = Math.sqrt(4-u*u); | |
ctx.save(); | |
ctx.beginPath(); | |
ctx.moveTo(0,0); | |
for(let [r, a] of [ | |
[v, A90], | |
[u, A60],[u, -A90], | |
[v, A60],[v, A90], | |
[u,-A60],[u, A90], | |
[v,-A60],[v, A90], | |
[u, A60],[2*u, A60], | |
[u, A90] | |
]) { | |
ctx.lineTo(0, r); | |
ctx.translate(0, r); | |
ctx.rotate(-a); | |
} | |
ctx.closePath(); | |
ctx.stroke(); | |
ctx.restore(); | |
} | |
// h(7,...) and h(8,...) supertiles. | |
function h(type, ctx, z, x, y, a) { | |
ctx.save(); | |
ctx.translate(x, y); | |
ctx.rotate(a); | |
if (z == 0) { | |
monotile(ctx); | |
} else { | |
for(let part=0; part < type - 1; part++) { | |
h(part == 0 ? 7 : 8, ctx, z - 1, ...origin(part, z - 1), ANGLES[part]); | |
} | |
} | |
ctx.restore(); | |
} | |
ctx.save(); | |
ctx.translate(500, 400); | |
ctx.scale(14, 14); | |
ctx.lineWidth = 1/14; | |
h(8, ctx, 2, 0, 0, 0); | |
ctx.restore(); | |
ctx.save(); | |
ctx.translate(500, 400); | |
ctx.scale(2, 2); | |
ctx.lineWidth = 1/4; | |
ctx.strokeStyle = 'red' | |
h(8, ctx, 4, 0, 0, 0); | |
ctx.restore(); |
Tried to simplify the mess by using matrix transforms, letting the canvas handle scaling, and shifting all the magic numbers into tables. It's less eye-hurting to read, anyway. (thanks to Craig Kaplan for pointing out the bugs in this version)
Since I wrote this I also came up with an L-system for drawing the tiling as a single line, which needs a lot less code (it's a lot slower and more repetitive tho) https://hachyderm.io/@bazzargh/110293551149890811
And in case that site disappears, the code inline:
import turtle
import math
def expand(order, a, s0, s1):
for op in s0 if order <= 0 else s1:
mono_op_map[op](order - 1, a)
# an earlier version of this used a stack, but it's unnecessary since
# I can just complete the loop round a tile to return the turtle to the branch point
mono_op_map = {
"a": lambda o, a: turtle.forward(a),
"b": lambda o, a: turtle.forward(a*math.sqrt(3)),
# U is drawn as "W--X--W", but I don't see how the rules would fit if I used that
"U": lambda o, a: expand(o, a, "a+++b--b+++a--a+++b", "V++R++U++V++W++V++R--U++V++W++V++R++U--V"),
"V": lambda o, a: expand(o, a, "b---a", "W++V++R++U++V++W++V++R--W"),
"W": lambda o, a: expand(o, a, "a+++b", "V++R++U++V++W++V++R--W++V++R++U++V++W--V"),
"R": lambda o, a: expand(o, a, "aa", "V++R++U++V++W++V++R--W"),
"+": lambda o, a: turtle.right(30),
"-": lambda o, a: turtle.left(30),
}
start = "U++V++W++V++R"
size = 6
order = 3
turtle.speed("fastest")
expand(order, size, start, start)
it does share with the above js code the opinion that the flipped tile is a hole; but it's better in that the a/b lengths can be changed
and this will still just work. This code is small enough that a variation (using single-character symbols for the turns) fits the BBC Basic code of it into a single toot https://hachyderm.io/@bazzargh/110295590546847505
the recursion is https://oeis.org/A001906 (with varying starting points). As mentioned there,
Limit_{n->infinity} a(n)/a(n-1) = 1 + phi = (3 + sqrt(5))/2
- 1 + phi is phi^2, this is exactly the expansion ratio mentioned in the monotile paper.