Log in to post a comment.

// LL 2021

const turtle = new Turtle();

const count = 12; // min=1 max=50 step=1
const iterations = 100; // min=1 max=200 step=1
const thickness = 2; // min=1 max=10 step=1
const direction = 0; // min=0 max=1 step=1 (In-Out,Out-In)
const all_flowers = 0; // min=0 max=1 step=1 (No,Yes)
const seed = 0; // min=0, max=100, step=1

const draw_hatching = 2; // min=0 max=3 step=1 (None,Background,Shape,Both)

let iteration;

const shapes = [];

const style = 1; // min=0 max=1 step=1 (Lines (fast),Polygons (slow))
let polygons;

Canvas.setpenopacity(style ? 1 : (1 / (iterations**0.5)));

function walk(i, t) {
    if (i == 0) {
        polygons = new Polygons();
        rng = undefined;
        iteration = 0;
    }

    if (shapes.length == 0)
    {
        iteration++;
        if (iteration > iterations) {
            if (draw_hatching&1) drawPoints([[-110, -110], [110, -110], [110, 110], [-110, 110]], 0.75)
            if (draw_hatching&1) drawPoints([[-110, -110], [110, -110], [110, 110], [-110, 110]], 0.75, -Math.PI/4)
            return false;
        }
        
        createShape(t);
    }
    
    const shape = shapes.shift();
    if (shape.draw()) shapes.push(shape);

    return true;
}

var dd = 0;

function createShape(t) {
    var count2 = count;
    if (random() < 0.5 && !(count&1)) count2 *= 0.5;
    else if (random() < 0.5) count2 *= 2;
    else if (random() < 0.5) count2 *= 4;
    const step = Math.PI * 2 / count2;

    const p = new Pattern(random());

    dd += 80 / iterations;
    const d = direction ? (75 - dd) : dd;
    const r = 0.1 + 10 * random() * Math.min(1, 50/d);
    const l = (p.sides < 0) ? 1 : (0.5 + random() * 4);
    const offset = 0; //Math.PI * (random() - .5) * 2 / count * Math.cos(t * Math.PI * 2);

    for (var i=0; i<count2; i++) {
        const dir = (i&1) ? 1 : -1;
        shapes.push(new Shape(i * step + dir * offset + step/2, d, r, r * l, p, i, count2));
    }
}

class Pattern {
    constructor(type) {
        const choices = [3, 4, 6, -2, -5, -12];
        if (type < 1) type *= choices.length;
        this.sides = choices[Math.floor(type) % choices.length];
        if (all_flowers) this.sides = -Math.abs(this.sides);
    }
    
    getPoints() {
        const points = [];
        if (this.sides > 0) {
            const step = Math.PI * 2 / this.sides;
            const start = Math.PI / 2;
            for (var a=0; a<Math.PI*2; a+=step) {
                const p = [Math.cos(a+start), Math.sin(a+start)];
                points.push(p);
            }
        } else {
            const step = Math.PI * 2 / 100;
            const start = Math.PI / 2;
            for (var a=0; a<Math.PI*2; a+=step) {
                const r = 0.1 + Math.abs(Math.sin(a*this.sides+Math.PI/2) * Math.cos(a/2)) * 0.9;
                const p = [r*Math.cos(a+start), r*Math.sin(a+start)];
                points.push(p);
            }
        }
        return points;
    }
}

class Shape {
    constructor(angle, distance, radius, length, pattern, slice, count2) {
        this.angle = angle + Math.PI;
        this.distance = distance;
        this.radius = radius;
        this.length = length;
        this.pattern_points = pattern.getPoints();
        this.thickness = 0;
        this.slice = slice;
        this.count = count2;
    }
    
    draw() {
        const radius = this.radius + this.thickness * 1.5;
        const length = this.length + this.thickness * 1.5;
        const points = this.pattern_points.map(p => [
            p[0] * radius, p[1] * length + this.distance, p[2]
        ]).map(p => [
            rotX(p[0], p[1], this.angle),
            rotY(p[0], p[1], this.angle),
            p[2]
        ]);
        
        const h = (draw_hatching&2) && (this.thickness & 1 || thickness == 1);
        drawPoints(points, h ? (0.25+this.distance/50) : 0, this.angle, this.slice, this.count);

        this.thickness++;
        return this.thickness < thickness;
    }
}

function split(p1, p2, t) {
    return [
        p1[0] * (1-t) + p2[0] * t,
        p1[1] * (1-t) + p2[1] * t
    ];
}

function rotX(x, y, a) { return Math.cos(a) * x - Math.sin(a) * y; }
function rotY(x, y, a) { return Math.sin(a) * x + Math.cos(a) * y; }

function drawPoints(dpoints, hatching=0, angle=Math.PI/4, slice=-1, count2=1) {
    if (style == 0) {
        turtle.jump(dpoints[dpoints.length-1]);
        dpoints.forEach(p=>turtle.goto(p));
    } else {
        if (slice >= 0)
        {
            const p1 = polygons.create();
            p1.addPoints(...dpoints);
            p1.addOutline();
            if (hatching) p1.addHatching(-angle, hatching);

            const step = Math.PI * 2 / count2;
            const start = -Math.PI / 2;
            const a1 = slice * step + start;
            const a2 = slice * step + step + start;
            const x1 = 500 * Math.cos(a1);
            const y1 = 500 * Math.sin(a1);
            const x2 = 500 * Math.cos(a2);
            const y2 = 500 * Math.sin(a2);
            const dpoints3 = [[0,0],[x1,y1],[x2,y2]];
            const p3 = polygons.create();
            p3.addPoints(...dpoints3);
            p1.boolean(p3, false);

            const p2 = polygons.create();
            const dpoints2 = clip(clip(dpoints, a1, 1), a2, 0);
            p2.addPoints(...dpoints2);

            polygons.draw(turtle, p1, false);
            polygons.draw(turtle, p2, true);
        } else {
            const p1 = polygons.create();
            p1.addPoints(...dpoints);
            p1.addOutline();
            if (hatching) p1.addHatching(-angle, hatching);
            polygons.draw(turtle, p1, false);
        }
    }
}

function clip(points, a, side) {
    const points2 = [];
    for (var i=0, c=points.length; i<c; i++) {
        const p0 = points[i];
        points2.push(p0);
        const p1 = points[(i + 1) % c];
        const rp0 = [ rotX(p0[0], p0[1], -a), rotY(p0[0], p0[1], -a) ];
        const rp1 = [ rotX(p1[0], p1[1], -a), rotY(p1[0], p1[1], -a) ];
        
        if (((rp0[1] >= 0) && (rp1[1] < 0)) || ((rp0[1] < 0) && (rp1[1] >= 0))) {
            const t = Math.abs(rp0[1] / (rp1[1] - rp0[1]));
            const p01 = [ p0[0] * (1-t) + p1[0] * t, p0[1] * (1-t) + p1[1] * t ];
            points2.push(p01);
        }
    }
    const points3 = [];
    points2.forEach(p => {
        const rp = [ rotX(p[0], p[1], -a), rotY(p[0], p[1], -a) ];
        if ((rp[1] < 0) != side || Math.abs(rp[1]) < 0.0001) points3.push(p);
    });

    return points3;
}

// Random with seed
var rng;
function random() { if (rng === undefined) rng = new RNG(seed); return rng.nextFloat(); }
function RNG(t){return new class{constructor(t){this.m=2147483648,this.a=1103515245,this.c=12345,this.state=t||Math.floor(Math.random()*(this.m-1))}nextFloat(){return this.state=(this.a*this.state+this.c)%this.m,this.state/(this.m-1)}}(t)}

////////////////////////////////////////////////////////////////
// Polygon Clipping utility code - Created by Reinder Nijhoff 2019
// https://turtletoy.net/turtle/a5befa1f8d
////////////////////////////////////////////////////////////////
function Polygons(){const t=[],s=class{constructor(){this.cp=[],this.dp=[],this.aabb=[]}addPoints(...t){let s=1e5,e=-1e5,h=1e5,i=-1e5;(this.cp=[...this.cp,...t]).forEach(t=>{s=Math.min(s,t[0]),e=Math.max(e,t[0]),h=Math.min(h,t[1]),i=Math.max(i,t[1])}),this.aabb=[(s+e)/2,(h+i)/2,(e-s)/2,(i-h)/2]}addSegments(...t){t.forEach(t=>this.dp.push(t))}addOutline(){for(let t=0,s=this.cp.length;t<s;t++)this.dp.push(this.cp[t],this.cp[(t+1)%s])}draw(t){for(let s=0,e=this.dp.length;s<e;s+=2)t.jump(this.dp[s]),t.goto(this.dp[s+1])}addHatching(t,e){const h=new s;h.cp.push([-1e5,-1e5],[1e5,-1e5],[1e5,1e5],[-1e5,1e5]);const i=Math.sin(t)*e,n=Math.cos(t)*e,a=200*Math.sin(t),p=200*Math.cos(t);for(let t=.5;t<150/e;t++)h.dp.push([i*t+p,n*t-a],[i*t-p,n*t+a]),h.dp.push([-i*t+p,-n*t-a],[-i*t-p,-n*t+a]);h.boolean(this,!1),this.dp=[...this.dp,...h.dp]}inside(t){let s=0;for(let e=0,h=this.cp.length;e<h;e++)this.segment_intersect(t,[.1,-1e3],this.cp[e],this.cp[(e+1)%h])&&s++;return 1&s}boolean(t,s=!0){if(Math.abs(this.aabb[0]-t.aabb[0])-(t.aabb[2]+this.aabb[2])>=0&&Math.abs(this.aabb[1]-t.aabb[1])-(t.aabb[3]+this.aabb[3])>=0)return this.dp.length>0;const e=[];for(let h=0,i=this.dp.length;h<i;h+=2){const i=this.dp[h],n=this.dp[h+1],a=[];for(let s=0,e=t.cp.length;s<e;s++){const h=this.segment_intersect(i,n,t.cp[s],t.cp[(s+1)%e]);!1!==h&&a.push(h)}if(0===a.length)s===!t.inside(i)&&e.push(i,n);else{a.push(i,n);const h=n[0]-i[0],p=n[1]-i[1];a.sort((t,s)=>(t[0]-i[0])*h+(t[1]-i[1])*p-(s[0]-i[0])*h-(s[1]-i[1])*p);for(let h=0;h<a.length-1;h++)(a[h][0]-a[h+1][0])**2+(a[h][1]-a[h+1][1])**2>=.001&&s===!t.inside([(a[h][0]+a[h+1][0])/2,(a[h][1]+a[h+1][1])/2])&&e.push(a[h],a[h+1])}}return(this.dp=e).length>0}segment_intersect(t,s,e,h){const i=(h[1]-e[1])*(s[0]-t[0])-(h[0]-e[0])*(s[1]-t[1]);if(0===i)return!1;const n=((h[0]-e[0])*(t[1]-e[1])-(h[1]-e[1])*(t[0]-e[0]))/i,a=((s[0]-t[0])*(t[1]-e[1])-(s[1]-t[1])*(t[0]-e[0]))/i;return n>=0&&n<=1&&a>=0&&a<=1&&[t[0]+n*(s[0]-t[0]),t[1]+n*(s[1]-t[1])]}};return{list:()=>t,create:()=>new s,draw:(s,e,h=!0)=>{for(let s=0;s<t.length&&e.boolean(t[s]);s++);e.draw(s),h&&t.push(e)}}}