Public
Edited
Dec 10, 2021
Importers
30 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
import {databases} from "@esperanc/synthesizing-periodic-tilings-of-the-plane"
Insert cell
Insert cell
// Euclidean coordinates of the basis
W = [Vec(1, 0), // w^0
Vec(0.8660254037844386, 0.5), // w^1
Vec(0.5, 0.8660254037844386), // w^2
Vec(0, 1) // w^3
]
Insert cell
Insert cell
wpow = [[1,0,0,0], // w^0
[0,1,0,0], // w^1
[0,0,1,0], // w^2
[0,0,0,1], // w^3
[-1,0,1,0], // w^4
[0,-1,0,1], // w^5
[-1, 0, 0, 0], // w^6
[0, -1, 0, 0], // w^7
[0, 0, -1, 0], // w^8
[0, 0, 0, -1], // w^9
[1, 0, -1, 0], // w^10
[0, 1, 0, -1]] // w^11
Insert cell
Insert cell
// Lattice vector sum
sum = (a,b) => a.map((x,i) => x + b[i])
Insert cell
// dot product of two lattice vectors
dot = (a,b) => (a.map((x,i) => x*b[i]).reduce((x,y) => x+y))
Insert cell
// Lattice vector scale
scale = (a, s) => a.map(x=>x*s)
Insert cell
Insert cell
planeCoords = L => L.map((x,i) => W[i].scale(x)).reduce((a,b) => a.add(b))
Insert cell
function planeToTileCoords(tiling) {
let { T1, T2 } = tiling;
let { x: a, y: b } = planeCoords(T1);
let { x: c, y: d } = planeCoords(T2);
let m = [
[a, b],
[c, d]
];
let det = a * d - b * c;
let [[ai, bi], [ci, di]] = [
[d / det, -c / det],
[-b / det, a / det]
];

// Maps canvas coordinates back to tile coordinates
return (x, y) => [ai * x + bi * y, ci * x + di * y];
}
Insert cell
Insert cell
//
// Given a tiling which is to be rendered scaled by edgeSize within a canvas of size w by h, returns
// a list of polygons that intersect the canvas rectangle.
//
function coverRect(tiling, edgeSize, w, h) {
let imin = 0,
imax = 0,
jmin = 0,
jmax = 0;

let tileCoords = planeToTileCoords(tiling);
let corners = [Vec(0, 0), Vec(w, 0), Vec(0, h), Vec(w, h)];
for (let { x, y } of corners) {
let [i, j] = tileCoords(x / edgeSize, y / edgeSize);
if (i < imin) imin = i;
if (j < jmin) jmin = j;
if (i > imax) imax = i;
if (j > jmax) jmax = j;
}
imin = Math.floor(imin - 1);
imax = Math.ceil(imax + 1);
jmin = Math.floor(jmin - 1);
jmax = Math.ceil(jmax + 1);

let faces = tilingFaces(tiling);
let cover = [];
let rect = new MBR(...corners);

for (let i = imin; i <= imax; i++) {
for (let j = jmin; j <= jmax; j++) {
for (let f of translateFaces(tiling, faces, i, j)) {
let poly = f.map((L) => planeCoords(L).scale(edgeSize));

let polyMbr = new MBR(...poly);

if (rect.intersects(polyMbr)) cover.push(poly);
}
}
}
return cover;
}
Insert cell
Insert cell
plot = function (ctx, primitives) {
let {points=[], edges =[], faces=[]} = primitives;
let canvasCoords = L => modelview.apply(planeCoords(L))
// Draw points
for (let v of points) {
let p = canvasCoords(v);
ctx.beginPath ();
ctx.arc (p.x,p.y,4,0,2*Math.PI)
ctx.stroke()
ctx.fill()
}
// Draw edges
for (let [v1,v2] of edges) {
ctx.beginPath ();
let p1 = canvasCoords(v1); ctx.moveTo(p1.x,p1.y)
let p2 = canvasCoords(v2); ctx.lineTo(p2.x,p2.y)
ctx.stroke()
}
// Draw faces
for (let f of faces) {
ctx.beginPath ();
let p = canvasCoords(f[0]); ctx.moveTo(p.x,p.y)
for (let i = 1; i<f.length; i++) {
let p = canvasCoords(f[i]); ctx.lineTo(p.x,p.y)
}
ctx.lineTo(p.x,p.y)
ctx.fill()
ctx.stroke();

}
}
Insert cell
//
// Affine transformation matrix to map euclidian coordinates to canvas coordinates in the demo plots
//
modelview = {
let [w,h] = [640,480];
let margin = 80;
let {T1,T2} = tiling;
let xmin = 0, xmax = 0, ymin = 0, ymax = 0;
for (let L of [T1, T2, sum(T1,T2)]) {
let {x,y} = planeCoords (L);
xmin = Math.min(xmin,x);
xmax = Math.max(xmax,x);
ymin = Math.min(ymin,y);
ymax = Math.max(ymax,y);
}
w -= margin*2;
h -= margin*2;
let s = Math.min(w/(xmax-xmin),h/(ymax-ymin));
let [dw,dh] = [w-(xmax-xmin)*s, h-(ymax-ymin)*s]
return Matrix.translate(dw/2+margin,dh/2+margin).mult(Matrix.scale(s,s).mult(Matrix.translate(-xmin,-ymin)))
}
Insert cell
Insert cell
Insert cell
//
// Returns a 4-point circulation for the translation
// frame of the given tiling
//
tilingCell = function (tiling) {
let {T1,T2} = tiling;
return [[0,0,0,0],T1,sum(T1,T2),T2]
}
Insert cell
Insert cell
edgePlot = {
let ctx = DOM.context2d(640,480,1);
// Draw the fundamental translation cell
ctx.setLineDash([2,2]);
ctx.fillStyle = '#F0F0F0';
plot (ctx, {faces: [tilingCell(tiling)]})
// Draw the edges in the star of each seed
ctx.setLineDash([]);
let edges = tilingEdges(tiling)
plot (ctx,{edges})
return ctx.canvas
}
Insert cell
//
// Returns an array of edges connecting the seeds of the given tiling to
// themselves or to translated copies of the fundamental tile
// in neighboring cells
//
function tilingEdges (tiling) {
let {T1,T2,Seed} = tiling;

// Points in neighboring cells
let V = {}
for (let s of Seed) {
for (let i of [-1,0,1]) {
for (let j of [-1,0,1]) {
let p = sum(s,sum(scale(T1,i),scale(T2,j)))
V[p] = 1
}
}
}
let edges = []
for (let s of Seed) {
for (let pow of wpow) {
let sk = sum(s,pow);
if (V[sk] && s < sk) {
edges.push([s,sk])
}
}
}
return edges
}

Insert cell
Insert cell
facePlot = {
let ctx = DOM.context2d(640,480,1);
ctx.setLineDash([]);
ctx.fillStyle = "lightgray"
plot (ctx,{faces: tilingFaces(tiling)})
// Draw the fundamental translation cell
ctx.setLineDash([2,2]);
ctx.fillStyle = "rgba(200, 200, 200, 0.2)";
plot (ctx, {faces: [tilingCell(tiling)]})
return ctx.canvas
}
Insert cell
JSON.stringify(tilingFaces(tiling))
Insert cell
//
// Returns an array with the fundamental faces of the given tiling.
// A face is an array of lattice points
//
function tilingFaces (tiling) {
let {T1,T2,Seed} = tiling;
// Points in neighboring cells
let V = {}
for (let s of Seed) {
for (let [i,j] of [[-1,0],[0,0],[1,0],[-1,-1],[0,-1],
[1,-1],[-1,1],[0,1],[1,1]]) {
let p = sum(s,sum(scale(T1,i),scale(T2,j)))
V[p] = 1
}
}

// Computes one face with m points starting at anchor point v
// starting at edge with direction w^k
let face = (v,k,m) => {
let f = [v, sum(v,wpow[k])];
for (let i = 2; i<m; i++) {
k = (k+12/m)%12;
f.push (sum(f[i-1],wpow[k]));
}
return f
}
// Computes all faces in the main cell
let faces = [];
for (let s of Seed) {
let S = [];
for (let k = 0; k<6; k++) {
let pow = wpow[k];
let sk = sum(s,pow);
if (V[sk]) S.push (k)
}
for (let i = 0; i+1 < S.length; i++) {
let h = 6 - (S[i+1]-S[i]);
let m = 12/h;
faces.push(face(s,S[i],m))
}
}
return faces
}
Insert cell
//
// Returns a replica of faces translated i units in direction
// tiling.T1 and j units in direction tiling.T2
//
function translateFaces (tiling, faces, i, j) {
let TI = scale(tiling.T1,i);
let TIJ = sum (TI, scale (tiling.T2,j));
return faces.map(f => f.map(v => sum(v,TIJ)));
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more