Amsterdam

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

Log in to post a comment.

// 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
// 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(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);
}