mirror of
https://github.com/by-jp/www.byjp.me.git
synced 2025-08-10 14:55:41 +01:00
This standardises all my markdown files to have no leading newline after the `---` that ends the frontmatter. This makes display on Gemini (in which newlines matter) much more consistent! I can't spot any errors with the regex I used here, but there may be one or two articles that change shape — hopefully I can spot them and fix them!
214 lines
7.6 KiB
Markdown
214 lines
7.6 KiB
Markdown
---
|
|
title: Tiles
|
|
emoji: 🔲
|
|
summary: Shapes that fit together to create an infinite pattern.
|
|
tags:
|
|
- monotile
|
|
- shapes
|
|
- tiling
|
|
- colour
|
|
references:
|
|
chiral-aperiodic-monotile:
|
|
url: https://cs.uwaterloo.ca/~csk/spectre/
|
|
name: A chiral aperiodic monotile
|
|
rel: +accurate
|
|
---
|
|
## Aperiodic monotiles
|
|
|
|
{{< figure src="../spectre.webp" caption="The spectre aperiodic monotile, with \"odd\" tiles shaded." >}}
|
|
|
|
2023 was a good year for tiling! The Einstein hat and Sprectre aperiodic monotiles were discovered and [documented](https://cs.uwaterloo.ca/~csk/spectre/). These are special because they've been proven to _never repeat_. There's something really quite wonderful about that!
|
|
|
|
I've designed 3D-printed cookie cutters that could make ceramic tiles with these, but I've not reached out to my local potters to ask for some time to make them — and I haven't figured out the 'rules' for placing a tile next to others, some placings definitely end up making tilings that can't be completed.
|
|
|
|
## Spectre + Tantrix
|
|
|
|
I wrote a little code a few weekends ago to try and figure out if I could make a Spectre tiling that also had lines that travelled across the tiles — something like a tiling game I used to play called [Tantrix](https://www.tantrix.com/).
|
|
|
|
Though there are some lines that close (you can see some here) I have a _hunch_ that there may be provably non-zero many that never close. I have no idea how to go about proving that though!
|
|
|
|
{{< figure src="../spectre.svg" title="My spectre & tantrix crossover tiling, each one with a #colour chosen for its angle of rotation." alt="A computer-generated tiling of 20 identical 14-sided irregular shapes. They vary in light pastel colours. Each of the 14 sides inside each shape is connected with a different side by a white line, making a big but pretty knot of white lines connected through each of the shapes.">}}
|
|
|
|
The code for making this is below; but it's definitely not polished!
|
|
|
|
<details>
|
|
<summary>Code for creating Spectre + Tantrix crossover</summary>
|
|
|
|
```ruby
|
|
require 'victor'
|
|
include Victor
|
|
|
|
class Spectre
|
|
attr_reader :points
|
|
|
|
# Angle 1 is the angle between side 1 and side 2 etc.
|
|
ANGLES = [90, 60, -90, 60, 0, 60, 90, -60, 90, -60, 90, 60, -90, 60].freeze
|
|
SIDE_LENGTH = 25
|
|
LINE_WIDTH = 2.5
|
|
CONTROL_LEN_MULT = 0.4
|
|
|
|
SHOW_NAME = false
|
|
|
|
ARCS = [
|
|
[7, 9],
|
|
[3, 11],
|
|
[0, 13],
|
|
[2, 8],
|
|
[5, 10],
|
|
[4, 6],
|
|
[1, 12],
|
|
]
|
|
|
|
def initialize(origin, corner_index, rotation, name: nil, show_corner_indeces: false)
|
|
@show_corner_indeces = show_corner_indeces
|
|
@hue = ANGLES[corner_index..].sum(-rotation) % 360
|
|
@indexes = (0...ANGLES.length).cycle.take(corner_index + ANGLES.length)[corner_index..]
|
|
@name = name || (@@i ||= 1).to_s
|
|
|
|
previous_angle = -rotation
|
|
@points = @indexes.each_with_object([[origin[0], origin[1]]]) do |idx, acc|
|
|
previous_angle += ANGLES[idx]
|
|
ang = previous_angle / 180.0 * Math::PI
|
|
acc.push([
|
|
acc.last[0] + SIDE_LENGTH*Math.sin(ang),
|
|
acc.last[1] + SIDE_LENGTH*Math.cos(ang)
|
|
])
|
|
end
|
|
|
|
@background = "hsl(#{@hue}, 100%, 85%)"
|
|
# @background = "hsl(#{(@hue / 90).floor * 90}, 100%, 85%)"
|
|
# @background = "hsl(23, 100%, 72%)"
|
|
|
|
@@i += 1
|
|
end
|
|
|
|
def point(idx) = @points[@indexes.index(idx)]
|
|
|
|
def between(idx1, idx2) = [
|
|
point(idx1)[0] + (point(idx2)[0] - point(idx1)[0])/2,
|
|
point(idx1)[1] + (point(idx2)[1] - point(idx1)[1])/2
|
|
]
|
|
|
|
def normal_point(idx1, idx2, n_dist)
|
|
b = between(idx1, idx2)
|
|
p1 = point(idx1)
|
|
p2 = point(idx2)
|
|
dx = p2[0] - p1[0]
|
|
dy = p2[1] - p1[1]
|
|
b_ang = Math.atan2(dy, dx)
|
|
b_dist = Math.sqrt(dx*dx + dy*dy) / 2
|
|
|
|
n_ang = Math.atan2(n_dist, b_dist)
|
|
|
|
t_ang = b_ang - n_ang
|
|
t_dist = Math.sqrt(n_dist*n_dist + b_dist*b_dist)
|
|
n_x = Math.cos(t_ang) * t_dist
|
|
n_y = Math.sin(t_ang) * t_dist
|
|
[p1[0] + n_x, p1[1] + n_y]
|
|
end
|
|
|
|
|
|
def svg
|
|
SVG.new.tap do |svg|
|
|
svg.polygon points: @points, stroke: 'none', fill: @background
|
|
|
|
ARCS.each do |(i1, i2)|
|
|
i1next = (i1 + 1) % ANGLES.length
|
|
i2next = (i2 + 1) % ANGLES.length
|
|
p1 = between(i1, i1next)
|
|
p2 = between(i2, i2next)
|
|
dx = (p2[0] - p1[0])
|
|
dy = (p2[1] - p1[1])
|
|
mid_dist = Math.sqrt(dx*dx + dy*dy)
|
|
c1 = normal_point(i1, i1next, mid_dist*CONTROL_LEN_MULT)
|
|
c2 = normal_point(i2, i2next, mid_dist*CONTROL_LEN_MULT)
|
|
|
|
show_control_points = false
|
|
if show_control_points
|
|
svg.circle cx: p1[0], cy: p1[1], r: 2, fill: 'green'
|
|
svg.line x1: p1[0], y1: p1[1], x2: c1[0], y2: c1[1], stroke: 'green'
|
|
svg.circle cx: c1[0], cy: c1[1], r: 2, fill: 'green'
|
|
svg.circle cx: c2[0], cy: c2[1], r: 2, fill: 'green'
|
|
svg.line x1: p2[0], y1: p2[1], x2: c2[0], y2: c2[1], stroke: 'green'
|
|
svg.circle cx: p2[0], cy: p2[1], r: 2, fill: 'green'
|
|
end
|
|
|
|
svg.path d: "M#{p1[0]},#{p1[1]}C#{c1[0]} #{c1[1]},#{c2[0]} #{c2[1]},#{p2[0]} #{p2[1]}", stroke: @background, stroke_width: LINE_WIDTH*2, fill: 'none'
|
|
svg.path d: "M#{p1[0]},#{p1[1]}C#{c1[0]} #{c1[1]},#{c2[0]} #{c2[1]},#{p2[0]} #{p2[1]}", stroke: 'white', stroke_width: LINE_WIDTH, fill: 'none'
|
|
end
|
|
|
|
centre = between(2, 7)
|
|
|
|
if SHOW_NAME
|
|
svg.text(
|
|
@name,
|
|
x: centre[0],
|
|
y: centre[1],
|
|
font_family: 'arial',
|
|
font_size: 16,
|
|
text_anchor: "middle",
|
|
dominant_baseline: "middle"
|
|
)
|
|
end
|
|
|
|
svg.polygon points: @points, stroke: 'black', fill: 'none'
|
|
|
|
if @show_corner_indeces
|
|
@points[0..-2].each.with_index do |p, idx|
|
|
svg.circle cx: p[0], cy:p[1], r: 5, fill: "hsla(#{@hue}, 100%, 95%, 85%)"
|
|
svg.text(
|
|
@indexes[idx],
|
|
x: p[0],
|
|
y: p[1],
|
|
font_family: 'arial',
|
|
font_size: 6,
|
|
text_anchor: "middle",
|
|
dominant_baseline: "middle"
|
|
)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
def into(svg)
|
|
svg << self.svg
|
|
self
|
|
end
|
|
end
|
|
|
|
|
|
svg = Victor::SVG.new width: 475, height: 400
|
|
|
|
s1 = Spectre.new([100, 100], 0, 0).into(svg)
|
|
s2 = Spectre.new(s1.point(2), 8, 120).into(svg)
|
|
s3 = Spectre.new(s1.point(4), 8, 60).into(svg)
|
|
s4 = Spectre.new(s3.point(2), 10, 30).into(svg)
|
|
s5 = Spectre.new(s3.point(10), 8, 120).into(svg)
|
|
s6 = Spectre.new(s3.point(0), 2, 0).into(svg)
|
|
s7 = Spectre.new(s5.point(2), 0, 90).into(svg)
|
|
s8 = Spectre.new(s7.point(2), 10, 210).into(svg)
|
|
s9 = Spectre.new(s5.point(12), 0, -150).into(svg)
|
|
s10 = Spectre.new(s2.point(12), 6, -150).into(svg)
|
|
s11 = Spectre.new(s6.point(9), 3, 90).into(svg)
|
|
s12 = Spectre.new(s6.point(12), 0, 30).into(svg)
|
|
s13 = Spectre.new(s11.point(7), 3, 120).into(svg)
|
|
s14 = Spectre.new(s11.point(12), 6, 30).into(svg)
|
|
s15 = Spectre.new(s13.point(0), 6, 90).into(svg)
|
|
s16 = Spectre.new(s8.point(2), 0, 210).into(svg)
|
|
s17 = Spectre.new(s16.point(12), 6, 90).into(svg)
|
|
s18 = Spectre.new(s17.point(2), 0, 30).into(svg)
|
|
s19 = Spectre.new(s7.point(6), 0, 90).into(svg)
|
|
s20 = Spectre.new(s13.point(10), 10, 120).into(svg)
|
|
s21 = Spectre.new(s4.point(2), 10, 30).into(svg)
|
|
s22 = Spectre.new(s15.point(4), 8, 60).into(svg)
|
|
|
|
svg.save 'spectre'
|
|
```
|
|
|
|
</details>
|
|
|
|
## Mosaics
|
|
|
|
I've been toying with the idea of creating ceramic tiles with glazes specially chosen/mixed to have levels of reflectivity to sodium light at λ = ~589 nm that are different to their white/solar light grayscale mapping.
|
|
|
|
This would allow me to make a day/night split mosaic; in the day the full-colour mosaic would be of one picture, but by night the street lights' singular frequency lamps (if I can find anywhere that still uses sodium lamps!) would create a different grayscale image that would jump out!
|