Seasons 🌱🍂

My go at rendering trees

Todo: clean up code

Next up: Utilise the injectability of the functions responsible for the branching and growing of leaves which will result in creating different 'species' of trees. Also, make use of parameters. For now only 'SEASON' and 'seed'.

Log in to post a comment.

const seed = 0; //min=0 max=100 step=1
const SEASON = 1 //min=0 max=1 step=1 (Spring, Autumn)
// You can find the Turtle API reference here: https://turtletoy.net/syntax
Canvas.setpenopacity(.9);

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

let plants = [new Plant([0, 99], [0,-1.4])];

// The walk function will be called until it returns false.
function walk(i) {
    plants.forEach(i => i.step());
    plants = plants.filter(i => !i.done());
    return plants.length > 0;
    return false;
}


// Seedable random number generator by David Bau: http://davidbau.com/archives/2010/01/30/random_seeds_coded_hints_and_quintillions.html
!function(a,b,c,d,e,f,g,h,i){function j(a){var b,c=a.length,e=this,f=0,g=e.i=e.j=0,h=e.S=[];for(c||(a=[c++]);d>f;)h[f]=f++;for(f=0;d>f;f++)h[f]=h[g=s&g+a[f%c]+(b=h[f])],h[g]=b;(e.g=function(a){for(var b,c=0,f=e.i,g=e.j,h=e.S;a--;)b=h[f=s&f+1],c=c*d+h[s&(h[f]=h[g=s&g+b])+(h[g]=b)];return e.i=f,e.j=g,c})(d)}function k(a,b){var c,d=[],e=typeof a;if(b&&"object"==e)for(c in a)try{d.push(k(a[c],b-1))}catch(f){}return d.length?d:"string"==e?a:a+"\0"}function l(a,b){for(var c,d=a+"",e=0;e<d.length;)b[s&e]=s&(c^=19*b[s&e])+d.charCodeAt(e++);return n(b)}function m(c){try{return o?n(o.randomBytes(d)):(a.crypto.getRandomValues(c=new Uint8Array(d)),n(c))}catch(e){return[+new Date,a,(c=a.navigator)&&c.plugins,a.screen,n(b)]}}function n(a){return String.fromCharCode.apply(0,a)}var o,p=c.pow(d,e),q=c.pow(2,f),r=2*q,s=d-1,t=c["seed"+i]=function(a,f,g){var h=[];f=1==f?{entropy:!0}:f||{};var o=l(k(f.entropy?[a,n(b)]:null==a?m():a,3),h),s=new j(h);return l(n(s.S),b),(f.pass||g||function(a,b,d){return d?(c[i]=a,b):a})(function(){for(var a=s.g(e),b=p,c=0;q>a;)a=(a+c)*d,b*=d,c=s.g(1);for(;a>=r;)a/=2,b/=2,c>>>=1;return(a+c)/b},o,"global"in f?f.global:this==c)};if(l(c[i](),b),g&&g.exports){g.exports=t;try{o=require("crypto")}catch(u){}}else h&&h.amd&&h(function(){return t})}(this,[],Math,256,6,52,"object"==typeof module&&module,"function"==typeof define&&define,"random");
Math.seedrandom('Generating trees in spring or autumn with this seedphrase' + seed);
// Add a seed in seedrandom, then Math.random will use this seed


function Plant(position, direction, branchFunction = null, leafFunction = null ) {

    const normalize2 = (a) => scale2(a, 1/length2(a));
    const length2 = (a) => Math.hypot(...a);
    const lengthSquared2 = (a) => dot2(a,a);
    const rotationMatrix2 = (radians) => [Math.cos(radians), -Math.sin(radians), Math.sin(radians), Math.cos(radians)];
    const transform2 = (matrix, a) => [matrix[0]*a[0]+matrix[2]*a[1], matrix[1]*a[0]+matrix[3]*a[1]];
    const scale2 = (a, scalar) => [a[0]*scalar,a[1]*scalar];
    const add2 = (a,b) => [a[0]+b[0],a[1]+b[1]];
    const sub2 = (a,b) => [a[0]-b[0],a[1]-b[1]];
    const dist2 = (a,b) => Math.hypot(...sub2(a,b));
    const lerp2 = (a,b,t) => [a[0]*(1-t)+b[0]*t,a[1]*(1-t)+b[1]*t];
    const dot2 = (a,b) => a[0]*b[0]+a[1]*b[1];
    const angle2 = (a) => Math.atan2(-a[1],a[0]);
    
    class Leaf {
        constructor(position, direction, fn) {
            this.position = position;
            this.direction = direction;
            
            this.pts = [];
            for(let i = 0; i <= 30; i++) {
                this.pts.push(fn(i/30*Math.PI*2));
            }
        }
        draw(leafRotation = [1,0,0,1]) {
            const turtle = new Turtle();
    
            turtle.jump(add2(this.pts[0], this.position));
            this.pts
                .map(i => scale2(i, length2(this.direction))) //scale to direction length
                .map(i => [i[0], i[1]*.5]) //flatten (look from side to leaf)
                .map(i => transform2(rotationMatrix2(angle2(this.direction)), i)) //rotate to direction
                .map(i => transform2(leafRotation, i))
                .map(i => add2(this.position, i))
                .forEach(i => turtle.goto(i)); //draw
        }
    }
    
    class Branch {
        constructor(parent, position, direction, branchFunction, leafFunction) {
            this.done = false;
            this.parent = parent;
            this.direction = direction;

            this.turtle = new Slowbro(position);

            branchFunction.bind(this);
            this.branch = branchFunction;
            leafFunction.bind(this);
            this.leaf = leafFunction;

            this.stepsSinceLastBranchLeft = 0;
            this.stepsSinceLastBranchRight = 0;
            this.leafLeft = 1;
            
            this.growLength = 0;
            this.drawnLeaf = 0;
        }
        createBranch(parent, position, direction, branchFunction) {
            return new Branch(parent, position, direction, branchFunction, this.leaf);
        }
        getThickness() {
            return length2(this.direction)**2 * 30;
        }
        addBranch(branch) {
            this.parent.addBranch(branch);
        }
        step() {
            if(!this.done) {
                this.turtle.thickness = this.getThickness();

                let b = this.branch(this, this.stepsSinceLastBranchLeft++, true);
                if(b !== null) {
                    this.stepsSinceLastBranchLeft = 0;
                    this.addBranch(b);
                }
                b = this.branch(this, this.stepsSinceLastBranchRight++, false);
                if(b !== null) {
                    this.stepsSinceLastBranchRight = 0;
                    this.addBranch(b);
                }
                
                const l = length2(this.direction);
                this.turtle.goto(add2(this.turtle.pos(), this.direction));
                this.direction = scale2(this.direction, .992);
                this.done = l < .1;
                
                this.growLength += l;
                
                this.leaf(this)
            }
        }
        done() {
            return this.done;
        }
    }
    class Plant {
        constructor(position, direction, branchFunction, leafFunction) {
            this.branches = [new Branch(this, position, direction, branchFunction, leafFunction)];
            //this.branchFunction = branchFunction;
        }
        step() {
            this.branches = this.branches.filter(i => !i.done);
            this.branches.forEach(i => i.step());
        }
        done() {
            return this.branches.length == 0;
        }
        addBranch(branch) {
            this.branches.push(branch);
        }
    }
    

    if(branchFunction === null) {
        branchFunction = (parent, stepsSinceLastBranch, isLeft) => {
            const l = length2(parent.direction);
            if(Math.random() > l**2.7||
                (l < .01 || .9 < l) ||
                stepsSinceLastBranch < 10 + Math.random()*30
            ) { return null; }
            
            let angle = (isLeft? 1: -1) //left or right
                * (Math.PI / 8) //fixed part
                + ((Math.random() * 2 - 1) * Math.PI / 16) //random part
                
            const branch = parent.createBranch(
                parent,
                parent.turtle.pos(),
                transform2(
                    rotationMatrix2(angle),
                    scale2(parent.direction, ((Math.random()*.3) + .7) * 1.1)
                ),
                branchFunction
            );
            
            const ratio = branch.getThickness() / parent.getThickness();
            
            parent.direction = transform2(
                rotationMatrix2(angle * ratio * -.7),
                parent.direction
            );
            
            return branch;
        }
    }
    
    if(leafFunction == null) {
        leafFunction = (branch) => {
            if(this.fallenLeafs == undefined) {
                this.fallenLeafs = Array.apply(null,{length: 200}).map(i => 0);
            }
            const leafPosition = SEASON == 0? branch.turtle.pos(): [branch.turtle.pos()[0], 100 + (this.fallenLeafs[(branch.turtle.pos()[0] | 0) + 100]%3==0? -this.fallenLeafs[(branch.turtle.pos()[0] | 0) + 100]*.2: 100)];
            if(branch.done) {
                const leaf = new Leaf(leafPosition, scale2(branch.direction, 30), (t) => [(1-Math.cos(t))/2, Math.sin(t)**3/4]);
                leaf.draw();
            } else if(length2(branch.direction) < .4 && (branch.growLength | 0) - 1 > branch.drawnLeaf) {
                const leafRotation = rotationMatrix2(Math.PI/4*branch.leafLeft);
                
                const leaf = new Leaf(leafPosition, scale2(normalize2(branch.direction), 3), (t) => [(1-Math.cos(t))/2, Math.sin(t)**3/4]);
                leaf.draw(leafRotation);
                branch.leafLeft*=-1;
                branch.drawnLeaf = branch.growLength | 0;
                
                this.fallenLeafs[(branch.turtle.pos()[0] | 0) + 100]++;
            }
        }
    }


    return new Plant(position, direction, branchFunction, leafFunction);
}

//https://turtletoy.net/turtle/cfe9091ad8
//https://turtletoy.net/turtle/ce083b5113
////////////////////////////////////////////////////////////////
// Slowbro utility code. Created by Lionel Lemarie 2021
// Based on Slowpoke by Reinder, which removes most duplicate
// lines Slowbro adds optional thickness to the lines
// Jurgen (2022) Modified thickness to center of line
////////////////////////////////////////////////////////////////
function Slowbro(x, y) {
    const linesDrawn = {};
    class Slowbro extends Turtle {
        constructor(x, y) {
        	super(x, y);
        	this.thickness = 1; this.offset = 0.12;
			this.slowpoke_skip = this.slowpoke_draw = 0;
        }
        goto(x, y) {
            if (Array.isArray(x)) { y = x[1]; x = x[0]; }
            const ox = this.x(), oy = this.y();
            if (this.isdown()) {
                const p = [x, y], o = [ox, oy];
                const h1 = o[0].toFixed(2) + '_' + p[0].toFixed(2) + o[1].toFixed(2) + '_' + p[1].toFixed(2);
                const h2 = p[0].toFixed(2) + '_' + o[0].toFixed(2) + p[1].toFixed(2) + '_' + o[1].toFixed(2);
                if (linesDrawn[h1] || linesDrawn[h2]) {
                    super.up(); super.goto(p); super.down();
                    this.slowpoke_skip++;
                    return;
                }
                linesDrawn[h1] = linesDrawn[h2] = true;
                this.slowpoke_draw++;
/*                
            	for (var dx = this.thickness-1; dx >=0 ; dx--) {
            		for (var dy = this.thickness-1; dy >= 0; dy--) {
            			if (!dx && !dy) continue;
            			super.goto( x + dx * this.offset,  y + dy * this.offset);
            			super.goto(ox + dx * this.offset, oy + dy * this.offset);
            		}
            	}
*/            	
            	const thickHalf = Math.floor(this.thickness/2);
                
            	for (var dx = thickHalf - 1; dx >=-thickHalf ; dx--) {
            		for (var dy = thickHalf - 1; dy >=-thickHalf; dy--) {
            			if (!dx && !dy) continue;
            			super.goto( x + dx * this.offset,  y + dy * this.offset);
            			super.goto(ox + dx * this.offset, oy + dy * this.offset);
            		}
            	}

            } 
            super.goto(x, y);
        }
    }
    return new Slowbro(x, y);
}