Inspired by twitter.com/cmonmatt…/1249803268571660288
#escher
Log in to post a comment.
const grid = 7; // min=3, max=17, step=2
const shape = 6; // min=4, max=12, step=2
const scale = 400 / grid;
const turtle = new Slowpoke();
const walk = i => {
const x = (i % grid) - (grid/2|0);
const y = (i/grid|0) - (grid/2|0);
// hexagon
let h = .75;
let w = 3/8 * (h/(Math.sqrt(3)/4));
let center = [x*2*w + (y%2)*w, y*3/2*h];
let shapes = [];
if (shape == 4) { // square
shapes = [[x-.5, y+.5], [x-.5,y-.5], [x+.5,y-.5], [x+.5,y+.5]];
center = [x,y];
} else if (shape == 6) { // hexagon
shapes = [[0,-h],[-w,-h/2],[-w,h/2],[0,h],[w,h/2],[w,-h/2]].map(p => add(center, p));
} else { // octagon-ish
for(let o = 0; o < shape; o++) {
let a = (o/shape + (1/ (shape*2))) * Math.PI*2;
if (shape == 8) {
h = 0.7033
w = 0.666
} else if (shape == 10) {
w = 0.69
h = 0.655
} else if (shape >= 12) {
w = h = 2/3 + 0.0056;
}
shapes.push(add(center, [Math.sin(a) * h, Math.cos(a) * w]));
}
}
// triangles inside hexagon
const triangles = shapes.map((p1, idx, s) => {
const p2 = s[(idx + 1) % s.length];
return (idx % 2 == 0) ? [ center, p2, p1 ] : [ center, p1, p2 ];
});
const showPaths = 1; // min=0, max=1, step=1
if (showPaths) {
// line paths
const paths = [
// patches
[[7,0], [8,1]],
[[7,4], [7,5]],
// stairs side
[[3,3], [3,2], [4,2],[4,0], [3,0], [3,1],[2,1], [2,2]],
[[10,5],[9,4], [8,4], [7,3],[7,4],[8,5],[9,5],[10,6]],
// stairs
[ [4,2], [3,2], [6,5], [7,5], [4,2] ],
[ [3,3], [6,6], [6,5], [3,2], [3,3] ],
[ [9,4], [9,1], [8,1], [8,4], [9,4] ],
[ [9,4], [10,5], [10,2], [9,1], [9,4] ],
];
while (paths.length) {
const path = paths.pop();
const shape = triangles.map(p => path.map(gridPos => getPosInTriangle(gridPos, p)));
shape.forEach(pts => drawPointsScaled(scale, pts, turtle));
}
}
const showDots = 1; // min=0, max=1, step=1
if (showDots) {
// dots inbetween
const gridPoints = [
[0,0],
[1,0], [1,1],
[2,0],
[8,0], [9,0],[10,0],[10,1],
[7,6], [7,7],
[8,6], [8,7], [8,8],
[9,6], [9,7], [9,8], [9,9],
[10,7], [10,8], [10,9], [10,10],
];
while (gridPoints.length) {
const pos = gridPoints.pop();
triangles.forEach(p => {
turtle.jump(scl(getPosInTriangle(pos, p), scale));
turtle.circle(0.11);
});
}
}
// triangle lines
const showTriangles = 0; // min=0, max=1, step=1
if (showTriangles) {
triangles.forEach(triangle => {
drawPointsScaled(scale, [...triangle, triangle[0]], turtle);
});
}
return i < grid * grid - 1;
}
function getPosInTriangle(pos, triangle) {
const totalSegments = 10;
const [p1,p2,p3] = triangle;
const d1 = pos[0] / totalSegments;
const d2 = pos[1] / totalSegments;
const dx = lrp(p1, p3, shape>1 ? d1 : 1-d1);
const dy = lrp(p1, p2, shape>1 ? 1-d2 : d2);
return sub(add(dx, dy), p1);
}
function drawPointsScaled(scale, points, turtle) {
drawPoints(points.map(p => scl(p,scale)), turtle);
}
function drawPoints(points, turtle) {
if (points.length > 0) turtle.jump(points[0]);
points.forEach((point, idx) => { if (idx > 0) turtle.goto(point);});
}
// vec2 functions
function vec2(a) { return [a,a]; }
function scl(a,b) { return [a[0]*b, a[1]*b]; }
function add(a,b) { return [a[0]+b[0], a[1]+b[1]]; }
function sub(a,b) { return [a[0]-b[0], a[1]-b[1]]; }
function dot(a,b) { return a[0]*b[0] + a[1]*b[1]; }
function len(a) { return Math.sqrt(a[0]**2 + a[1]**2); }
function nrm(a) { return scl(a, 1/len(a)); }
function lrp(a,b,f) { return [a[0]*f+b[0]*(1-f), a[1]*f+b[1]*(1-f)]; }
function eql(a,b) { return a[0]==b[0] && a[1]==b[1] }
////////////////////////////////////////////////////////////////
// Slowpoke utility code. Created by Reinder Nijhoff 2019
// https://turtletoy.net/turtle/cfe9091ad8
////////////////////////////////////////////////////////////////
function Slowpoke(x, y) {
const linesDrawn = {};
class Slowpoke extends Turtle {
goto(x, y) {
const p = Array.isArray(x) ? [...x] : [x, y];
if (this.isdown()) {
const o = [this.x(), this.y()];
const h1 = o[0].toFixed(2)+'_'+p[0].toFixed(2)+o[1].toFixed(2)+'_'+p[1].toFixed(2);
const h2 = p[0].toFixed(2)+'_'+o[0].toFixed(2)+p[1].toFixed(2)+'_'+o[1].toFixed(2);
if (linesDrawn[h1] || linesDrawn[h2]) {
super.up();
super.goto(p);
super.down();
return;
}
linesDrawn[h1] = linesDrawn[h2] = true;
}
super.goto(p);
}
}
return new Slowpoke(x,y);
}