www.byjp.me/content/memex/art-design/tiles.md
JP Hastings-Spital b8cc9ee66d Remove leading newlines
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!
2024-06-20 13:40:54 +01:00

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!