Plant 🌱

* Pilea Peperomioides
* Pannekoekenplant

Log in to post a comment.

// Forked from "Ellipse" by markknol
// https://turtletoy.net/turtle/88914e0941

const turtle = new Turtle();
const polygons = new Polygons();

let minRadius = 8; // min=2, max=40, step=1
let maxRadius = 18; // min=2, max=40, step=1

const segments = 20;

let useHatching = 0; // min=0, max=1, step=1
let usePolygons = 1; // min=0, max=1, step=1
let debug = 0; // min=0, max=1, step=1


function walk(i) {
    let order0 = [];
    let order1 = [];
    let order2 = [];
    let order3 = [];

    let spreadX = 50;
    let spreadY = [10, -40];
    let trunkY = [range(-50,-20), 10];
    
    let total = 8; // min=0, max=20, step=1
    for(let ii=0; ii<total/2;ii++) {
        let t = ii/(total/2);
        
        let baseX = lerp(-1,1,t);
        let baseY = range(trunkY[0], trunkY[1]);
        
        let baseSize = range(minRadius, maxRadius);
        let sizeScale = lerp(0.25, 1.0, baseSize/Math.max(minRadius,maxRadius));
        let x = lerp(-spreadX*sizeScale,spreadX*sizeScale, t+range(-0.1,0.1));
        let y = baseY + lerp(spreadY[0],spreadY[1],1-(Math.random()*Math.random()));
        let radiusX = baseSize;//lerp(baseSize/2,baseSize, pingpong(t+range(-0.1,0.1)));
        let radiusY = baseSize*1.2;//lerp(13,13, pingpong(t));
        let angle = lerp(1, -1, t);// + range(-1,1));
        
        order1.push(() => drawStem([[baseX, baseY], [lerp(baseX,x,lerp(0.5, 0.25, pingpong(t))), lerp(baseY,y,0.5) + range(y > 0 ? 30 : -30, 0)], [x,y]]));
        order1.push(() => drawLeaf(x, y, radiusX, radiusY, angle, segments));
    }
    
    let trunk = () => drawStem([[0, trunkY[0]], [0, trunkY[1]]], 3);
    
    for(let ii=0; ii<total/2;ii++) {
        let t = ii/(total/2);
        
        let baseX = lerp(-1,1,t);
        let baseY = range(trunkY[0], trunkY[1]);
        
        let baseSize = range(minRadius, maxRadius);
        let sizeScale = lerp(0.25, 1.0, baseSize/Math.max(minRadius,maxRadius));
        let x = lerp(-spreadX*sizeScale,spreadX*sizeScale, t+range(-0.1,0.1));
        let y = baseY + lerp(spreadY[0],spreadY[1], 1-(Math.random()*Math.random()));
        let radiusX = baseSize;//lerp(baseSize/2,baseSize, pingpong(t+range(-0.1,0.1)));
        let radiusY = baseSize*1.2;//lerp(13,13, pingpong(t));
        let angle = lerp(1, -1, t);// + range(-1,1));
        
        order2.push(() => drawStem([[baseX, baseY], [lerp(baseX,x,lerp(0.5, 0.25, pingpong(t))), lerp(baseY,y,0.5) + range(y > 0 ? 30 : -30, 0)], [x,y]]));
        order3.push(() => drawLeaf(x, y, radiusX, radiusY, angle, segments));
    }
    
    // lol, poor man depth sorting
    let shifts = order3.length/2|0;
    while(shifts--) {
        order3.unshift(order3.pop())
        order2.unshift(order2.pop())
        order1.unshift(order1.pop())
        order0.unshift(order0.pop())
    }
    let order = [
        ...order3,    
        () => drawPot(0,0,35,66),
        ...order2,
        trunk,
        ...order1
    ];
    order.forEach(cb=>cb());
}

function drawLeaf(x = 0, y = 0, radiusX = 40, radiusY = 20, angle = 0, segments = 15) {
    let path = ellipse(x,y,radiusX,radiusY,angle,segments);
    let hatchAngle = Math.atan2(y,x);
    const precision = 6;
    const smoothness = 4;
    let transformers = [
        closePath,
        path => useHatching ? crossHatch(path, 0) : null,
        path => drawPoints(path, useHatching ? [hatchAngle,0.5] : null),
        path => debug ? drawDebugCircles(path) : null,
    ];
    transform(path, transformers);
}

function drawPot(x,y,w,h, slant = 0.7,top=0.2) {
    let path = [
        [x - w, y],
        [x + w, y],
        [x + w, y+h*top],
        [x + w*slant, y+h],
        [x - w*slant, y+h],
        [x - w, y+h*top],
    ];
    const precision = 6;
    const smoothness = 4;
    let transformers = [
        path => normalizePath(path, precision, true),
        //path => smoothPath(path, smoothness, true, false),
        closePath,
        path => drawPoints(path, [0, 2.5]),
        path => debug ? drawDebugCircles(path) : null,
    ];
    transform(path, transformers);
}

function drawStem(path, lineWidth = 1.25) {
    const precision = 6;
    const smoothness = 4;

    let transformers = [
        path => normalizePath(path, precision, true),
        path => smoothPath(path, smoothness, false, true),
        path => extrude(path, r => lerp(lineWidth/4, lineWidth, clamp(inverseLerp(1, 0.9, r))), true),
        closePath,
        path => useHatching ? crossHatch(path, 1) : null,
        path => drawPoints(path),
        path => debug ? drawDebugCircles(path) : null,
    ];
    transform(path, transformers);
}

function crossHatch(pts, mode = 0) {
    let halfLength = (pts.length/2)|0;
    pts.forEach((p,i) => {
        if (i < halfLength) {
            let p1 = pts[i];
            let p2 = mode ? pts[pts.length - i] : pts[(i+halfLength)%pts.length];
            if (p1&&p2) drawPoints([p1,p2]);
        }
    });
}

function drawPoints(pts, hatch1, hatch2) {
    if (usePolygons) {
        let p = polygons.create();
        p.addPoints(...pts);
        p.addOutline();
        if (hatch1) p.addHatching(...hatch1);
        if (hatch2) p.addHatching(...hatch2);
        polygons.draw(turtle,p);
    } else {
        pts.forEach((p,i) => i==0?turtle.jump(p):turtle.goto(p));
    }
}

function drawDebugCircles(pts,radius = 0.75) {
    pts.forEach(p => circle(p, radius));
}
function circle(p, radius = 1) {
    turtle.jump(p[0],p[1]-radius);
    turtle.circle(radius);
}

function ellipse(x = 0, y = 0, radiusX = 40, radiusY = 20, angle = 0, segments = 15) {
    return Array.from({length: segments}, (_, i) => (i / segments) * Math.PI*2).map(ang => ([
        x - (radiusY * Math.sin(ang)) * Math.sin(angle) + (radiusX * Math.cos(ang)) * Math.cos(angle),
        y + (radiusX * Math.cos(ang)) * Math.sin(angle) + (radiusY * Math.sin(ang)) * Math.cos(angle),
    ]));
}
function inverseLerp(a, b, t) {
    return (t - a) / (b -  a);
}
function lerp(a,b,t) {
    return a+(b-a)*t;
}
function range(a,b) {
    return lerp(a,b,Math.random());
}
function clamp(v, a=0,b=1) {
    [a,b] = [Math.min(a,b), Math.max(b,a)];
    return v<a ? a : (v > b ? b : v);
}
function pingpong(t) { 
    return (t > 0.5 ? 1 - t : t) * 2;
}

function transform(path, transformers) {
    transformers.forEach(transformer => {
        let transformed = transformer(path);
        if (transformed) path = transformed; // prevent transforms returning void
    });
    return path;
}

function normalizePath(path, minDist = 1, keepCorners = false) {
    const newPath = [];
    for (let i = 1; i < path.length; i++) {
        let prev = clone(path[i-1]);
        let curr = clone(path[i]);
        let delta = sub(curr, prev);
        let l = len(delta);
        let a = atan2(delta);
        if (keepCorners) newPath.push(clone(prev));
        while (l >= minDist) {
            moveTo(prev, a, minDist);
            newPath.push(clone(prev));
            l -= minDist;
        }
    }
    newPath.push(path[path.length-1]);
    return newPath;
}

function smoothPath(path, smoothness = 2, wrap = true, keepStartEnd = false) {
    let pp = clone(path)
    if (!smoothness) return path;
    let prevPos = null;
    for (let smooth = 0; smooth < smoothness; smooth++) {
        let prevPos;
        let nextPos
        path.forEach((pos, idx) => {
            if (idx > 0) {
                if (wrap) {
                    prevPos = idx === 0 ? path[path.length-1] : path[idx-1];
                    nextPos = idx === path.length-1 ? path[0] : path[idx+1];
                } else {
                    prevPos = idx === 0 ? path[idx] : path[idx-1];
                    nextPos = idx === path.length-1 ? path[path.length-1] : path[idx+1];
                }
                path[idx] = scl(add(add(prevPos, pos), nextPos), 1/3);
            }
        });
    }
    if (!keepStartEnd) return path;
    else return [pp[0],...path, pp[pp.length-1]];
}

function extrudePathSide(newPath, path, thickness, forward) {
	path.forEach((curr, idx) => {
		let t = thickness(forward ? idx / path.length : 1 - idx / path.length);
		if (forward) t *= -1;
		let next = path[idx + 1];
		let diff;
		if (idx == 0) {
			diff = sub(next, curr);
		} else {
		    let prev = path[idx - 1];
			if (!next) next = curr;
			diff = sub(next, prev);
		}
		let dir = atan2(diff) + (Math.PI * 0.5 * (!forward ? -1 : 1));
		newPath.push(moveTo(clone(curr), dir, t));
	});
}

function extrude(path, thickness, merged = false) {
    if (thickness === 0 || path.length < 2) return path;
	const newPath1 = [];
	extrudePathSide(newPath1, path, thickness, true);
	path = path.reverse();
	const newPath2 = [];
	extrudePathSide(newPath2, path, thickness, false);
	return merged ? [...newPath1, ...newPath2] :  [newPath1, newPath2];
}

function closePath(path)    { if (!isClosedPath(path)) path.push(path[0]); return path; }
function isClosedPath(path) { return path[0] === path[path.length-1]; }

function moveTo(a, angle, distance) { a[0] += Math.cos(angle) * distance; a[1] += Math.sin(angle) * distance; return a; }


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 atan2(a) { return Math.atan2(a[1], a[0]); }
function eql(a,b)    { return a[0]==b[0] && a[1]==b[1]; }
function clone(a) { return [...a]; }


////////////////////////////////////////////////////////////////
// Polygon Clipping utility code - Created by Reinder Nijhoff 2019
// (Polygon binning by Lionel Lemarie 2021)
// https://turtletoy.net/turtle/a5befa1f8d
////////////////////////////////////////////////////////////////
function Polygons(){const t=[],s=25,e=Array.from({length:s**2},t=>[]),n=class{constructor(){this.cp=[],this.dp=[],this.aabb=[]}addPoints(...t){let s=1e5,e=-1e5,n=1e5,h=-1e5;(this.cp=[...this.cp,...t]).forEach(t=>{s=Math.min(s,t[0]),e=Math.max(e,t[0]),n=Math.min(n,t[1]),h=Math.max(h,t[1])}),this.aabb=[s,n,e,h]}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,s){const e=new n;e.cp.push([-1e5,-1e5],[1e5,-1e5],[1e5,1e5],[-1e5,1e5]);const h=Math.sin(t)*s,o=Math.cos(t)*s,a=200*Math.sin(t),i=200*Math.cos(t);for(let t=.5;t<150/s;t++)e.dp.push([h*t+i,o*t-a],[h*t-i,o*t+a]),e.dp.push([-h*t+i,-o*t-a],[-h*t-i,-o*t+a]);e.boolean(this,!1),this.dp=[...this.dp,...e.dp]}inside(t){let s=0;for(let e=0,n=this.cp.length;e<n;e++)this.segment_intersect(t,[.1,-1e3],this.cp[e],this.cp[(e+1)%n])&&s++;return 1&s}boolean(t,s=!0){const e=[];for(let n=0,h=this.dp.length;n<h;n+=2){const h=this.dp[n],o=this.dp[n+1],a=[];for(let s=0,e=t.cp.length;s<e;s++){const n=this.segment_intersect(h,o,t.cp[s],t.cp[(s+1)%e]);!1!==n&&a.push(n)}if(0===a.length)s===!t.inside(h)&&e.push(h,o);else{a.push(h,o);const n=o[0]-h[0],i=o[1]-h[1];a.sort((t,s)=>(t[0]-h[0])*n+(t[1]-h[1])*i-(s[0]-h[0])*n-(s[1]-h[1])*i);for(let n=0;n<a.length-1;n++)(a[n][0]-a[n+1][0])**2+(a[n][1]-a[n+1][1])**2>=.001&&s===!t.inside([(a[n][0]+a[n+1][0])/2,(a[n][1]+a[n+1][1])/2])&&e.push(a[n],a[n+1])}}return(this.dp=e).length>0}segment_intersect(t,s,e,n){const h=(n[1]-e[1])*(s[0]-t[0])-(n[0]-e[0])*(s[1]-t[1]);if(0===h)return!1;const o=((n[0]-e[0])*(t[1]-e[1])-(n[1]-e[1])*(t[0]-e[0]))/h,a=((s[0]-t[0])*(t[1]-e[1])-(s[1]-t[1])*(t[0]-e[0]))/h;return o>=0&&o<=1&&a>=0&&a<=1&&[t[0]+o*(s[0]-t[0]),t[1]+o*(s[1]-t[1])]}};return{list:()=>t,create:()=>new n,draw:(n,h,o=!0)=>{reducedPolygonList=function(n){const h={},o=200/s;for(var a=0;a<s;a++){const c=a*o-100,r=[0,c,200,c+o];if(!(n[3]<r[1]||n[1]>r[3]))for(var i=0;i<s;i++){const c=i*o-100;r[0]=c,r[2]=c+o,n[0]>r[2]||n[2]<r[0]||e[i+a*s].forEach(s=>{const e=t[s];n[3]<e.aabb[1]||n[1]>e.aabb[3]||n[0]>e.aabb[2]||n[2]<e.aabb[0]||(h[s]=1)})}}return Array.from(Object.keys(h),s=>t[s])}(h.aabb);for(let t=0;t<reducedPolygonList.length&&h.boolean(reducedPolygonList[t]);t++);h.draw(n),o&&function(n){t.push(n);const h=t.length-1,o=200/s;e.forEach((t,e)=>{const a=e%s*o-100,i=(e/s|0)*o-100,c=[a,i,a+o,i+o];c[3]<n.aabb[1]||c[1]>n.aabb[3]||c[0]>n.aabb[2]||c[2]<n.aabb[0]||t.push(h)})}(h)}}}