Flower power 🪷

Not all combinations of settings work, but limits of settings cannot be dynamic.
Flower power 🪷 (variation)
Flower power 🪷 (variation)
Flower power 🪷 (variation)

Log in to post a comment.

const points = 16; //min=3 max=40 step=1
const circles = 8; //min=1 max=10 step=1
const cutoff = 1; //min=0 max=1 step=1 (circle, fit)
const cutin = 2; //min=0 max=2 step=1 (none, circle, fit)
const cutinSkip = 0;//min=0 max=10 step=1
const swingMode = 2; //min=0 max=2 step=1 (gradient, half gradient, toggle)
const nearHop = .33; //min=.25 max=.5 step=.01
const opacity = .015; //min=0.001 max=.5 step=.001
const inc_1000 = 1; //min=1 max=100 step=1
const leafs = 0;// min=0 max=1 step=1 (Sinoid, Inverse sinoid)

const maxR = 95;

// You can find the Turtle API reference here: https://turtletoy.net/syntax
Canvas.setpenopacity(opacity);

// Global code will be evaluated once.
const turtle = new Turtle();

const getCirclePoint = (radius, pos, location=[0,0], rotation=0) => [location[0] + radius * Math.sin(pos * 2 * Math.PI + rotation), location[1] + radius * -Math.cos(pos * 2 * Math.PI + rotation)];
const distro = (t) => leafs == 0? -Math.cos(t * Math.PI) * .5 + .5: 1 - Math.acos((t-.5)*2) / Math.PI;
const radii = Array.from({length: circles}).map((v, i) => maxR - i * maxR / circles).reverse();

let polygonOfDoom = [];
let polygonOfDoomVectors = [];
let polygonOfDoomVectorsBuffer = [];
let lastCircle = 0;

let cutinBoundaries = [];

// The walk function will be called until it returns false.
function walk(i) {
    const circle = i/points | 0;
    const rotation = (swingMode == 2? ((circle % 2) / 2) /points: (swingMode == 1?circle / circles / points: 2 * circle / circles / points));
    const r = radii[circle];//maxR;

    if(circle != lastCircle) {
        if(cutin == 2) {
            polygonOfDoom = cutinBoundaries.flatMap((v, i, a) => [
                v[0],
                segment_intersect2(v[0], v[1], a[(i+1)%a.length][0], a[(i+1)%a.length][2])
            ]).map((pt, i, a) => [pt, a[(i+1)%a.length]]);
            cutinBoundaries = [];
        } else if(cutin == 1) {
            polygonOfDoom = Array.from({length: (radii[circle - 1] * 2 * Math.PI) | 0})
                .map((v, i, a) => getCirclePoint(radii[circle - 1], i / a.length))
                .map((pt, i, a) => [pt, a[(i+1)%a.length]]);
        }
        polygonOfDoomVectorsBuffer.push(polygonOfDoom.map(v => [v[0], sub2(v[1], v[0])]));
        if(lastCircle >= cutinSkip) {
            polygonOfDoomVectors = polygonOfDoomVectorsBuffer.shift();
        }
        lastCircle = circle;
    }

    const start = (i%points) / points + rotation;
    const origin = getCirclePoint(r, start);
    
    const from = Math.max(1, Math.min(Math.ceil(points/2) - 1, Math.round(nearHop*points))) / points;
    const extent = 1 - 2 * from;

    const originFrom = getCirclePoint(r, start + from);
    const originTo = getCirclePoint(r, start + from + extent);
    const originFrom_From = getCirclePoint(r, start + from + from);
    const originTo_To = getCirclePoint(r, start + from + extent + from + extent);
    const intersection = segment_intersect2(originFrom, originFrom_From, originTo, originTo_To);
    const limitLines = [[originFrom, intersection], [intersection, originTo]];

    cutinBoundaries.push([origin, originFrom, originTo]);

    for(let x = 0; x <= 1; x+=inc_1000/1000) {
        const projection = getCirclePoint(r, start + from + extent * distro(x));
        const to = cutoff == 0? projection: limitLines.map(ll => segment_intersect2(origin, projection, ...ll)).filter(i => i).pop()
        
        const direction = sub2(to, origin);
        
        const fromTo = [origin];

        const intersections = polygonOfDoomVectors.map(v => intersect_info2(origin, direction, ...v)).filter(v => v !== false && 0 <= v[0] && v[0] <= 1 && 0 <= v[1] && v[1] <= 1).sort((a, b) => a[0] < b[0]? -1: 1);
        intersections.forEach(v => fromTo.push(v[2]));

        fromTo.push(to);
        
        fromTo.forEach((v, i) => i%2 == 0? turtle.jump(v): turtle.goto(v));
    }

    return i < circles * points - 1;
}

function add2(a, b) { return [a[0]+b[0], a[1]+b[1]]; }
function sub2(a, b) { return [a[0]-b[0], a[1]-b[1]]; }
function mul2(a, b) { return [a[0]*b[0], a[1]*b[1]]; }
function scale2(a, s) { return mul2(a, [s,s]); }
function lenSq2(a) { return a[0]**2+a[1]**2; }
function len2(a) { return Math.sqrt(lenSq2(a)); }
function intersect_info2(as, ad, bs, bd) {
    const d = [bs[0] - as[0], bs[1] - as[1]];
    const det = bd[0] * ad[1] - bd[1] * ad[0];
    if(det === 0) return false;
    const res = [(d[1] * bd[0] - d[0] * bd[1]) / det, (d[1] * ad[0] - d[0] * ad[1]) / det];
    return [...res, add2(as, scale2(ad, res[0]))];
}
function intersect_ray2(a, b, c, d) {
    const i = intersect_info2(a, b, c, d);
    return i === false? i: i[2];
}
function segment_intersect2(a,b,c,d, inclusive = true) {
    const i = intersect_info2(a, sub2(b, a), c, sub2(d, c));
    if(i === false) return false;
    const t = inclusive? 0<=i[0]&&i[0]<=1&&0<=i[1]&&i[1]<=1: 0<i[0]&&i[0]<1&&0<i[1]&&i[1]<1;
    return t?i[2]:false;
}