### Amsterdam

Based on line art by Regolo Bizzi - behance.net/regolo

// Amsterdam. Created by Reinder Nijhoff 2021 - @reindernijhoff
//
// https://turtletoy.net/turtle/d30c1379c9#

const turtle = new Slowpoke();

const shape = 1; // min=0, max=2, step=1 (Hexagon, Square, Triangle)
const recursion = 5; // min=1, max=10, step=1
const rotMode = 0; // min=0, max=4, step=1 (Mode 1, Mode 2, Mode 3, Mode 4, Mode 5)
const rotDist = 2; // min=1, max=5, step=0.01
const rotFalloff = .85; // min=0.5, max=1, step=0.01

if (rotMode === 4) {
Canvas.setpenopacity(.5);
}

function* drawPolygons() {
const ts  = Math.sin(Math.PI * 2 / 3);
const tss = 4/3;
// too much magic code here
const def =
shape == 0 ?
{
rotations: 6,
step: [.5, 0],
polygons: [[[0,-4/3*ts],[-1, 2/3*ts],[1, 2/3*ts]],[[0,2/3*ts],[1,-4/3*ts],[-1,-4/3*ts]]]
} : shape == 1 ?
{
rotations: 4,
step: [1, 0],
polygons: [[[-1,-1], [1,-1], [1,1], [-1,1]]]
} :
{
rotations: 4,
step: [1, 0],
polygons: [[[-2,1],[2,1],[0,-1]], [[-2,-1],[0,1],[2,-1]]]
};

for (let r = 0; r<recursion; r++) {
const s = Math.pow(0.5, r) * 50; // size of base shape for recursion level
const n = 1 + 4 * (Math.pow(2, r)-1); // number of shapes
const min_dist = rotDist * Math.pow(rotFalloff, r);

// start position of first shape of this recursion level
const start = shape == 0 ? sub(scale([-.5,ts], 100 - 2 * s), [0,-2/3*ts*s]) :
shape == 1 ? scale([-1,1], 100 - 1.5 * s) :
sub(scale([-1,1], 100 - 2 * s), [0,-.5*s]);

let   f = rotMode ? 1 : r % 2 == 0 ? 1 : -1;

for (let j=0; j<def.rotations; j++) {
if (rotMode >= 2) {
f = rotMode == 2 ? 1 : -1;
}
const mat = rot(j * Math.PI * 2 / def.rotations);

// draw all shapes for recursion level and rotate
for (let i=0; i<n; i++) {
const polygon = def.polygons[i % def.polygons.length];
const center  = add(start, scale(def.step, i*s));
drawPoly(polygon.map(c => trans(mat, add(center, scale(c, .5 * s)))), min_dist, f = -f);
rotMode === 4 && drawPoly(polygon.map(c => trans(mat, add(center, scale(c, .5 * s)))), min_dist, f = -f);
yield true;
}
}
}
}

const iterator = drawPolygons();

function walk(i) {
return !iterator.next().done;
}

function drawPoly(p, min_distance, rot) {
for(let r=0;r<100;r++) {
// draw polygon
turtle.jump(p[p.length-1]); p.forEach(c => turtle.goto(c));
// rotate polygon
const c = p; p = [];
for (let i=0; i<c.length; i++) {
const c0 = c[i];
const c1 = c[(i+rot+c.length) % c.length];
const d = dist(c0, c1);
if (d <= min_distance) return;
p[i] = lerp(c0, c1, min_distance / d);
}
}
}

//
// 2D Vector math
//

function rot(a) { return [Math.cos(a), -Math.sin(a), Math.sin(a), Math.cos(a)]; }
function trans(m, a) { return [m[0]*a[0]+m[2]*a[1], m[1]*a[0]+m[3]*a[1]]; }
function scale(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 dist(a,b) { return Math.hypot(...sub(a,b)); }
function lerp(a,b,t) { return [a[0]*(1-t)+b[0]*t,a[1]*(1-t)+b[1]*t]; }

////////////////////////////////////////////////////////////////
// Slowpoke utility code. Created by Reinder Nijhoff 2019
////////////////////////////////////////////////////////////////

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(8)+'_'+p[0].toFixed(8)+o[1].toFixed(8)+'_'+p[1].toFixed(8);
const h2 = p[0].toFixed(8)+'_'+o[0].toFixed(8)+p[1].toFixed(8)+'_'+o[1].toFixed(8);
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);
}