Cacti

Procedural Cacti

Log in to post a comment.

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

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


const cacti = 25; // min=1 max=100 step=1
const columns = 5; //min=1 max=10 step=1
const startX = -75; //min=-100 max=100 step=5
const startY = 95;  //min=-100 max=100 step=5
const offsetX = 37; //min=-100 max=100 step=1
const offsetY = 37; //min=-100 max=100 step=1

const trunkThickness = 4; //min=1 max=15 step=.1
const branchThickness = 2; //min=1 max=15 step=.1

const trunkHeightMin = 15; //min=5 max=100 step=1
const trunkHeightMax = 30; //min=5 max=100 step=1
const branchHeightMin = 2; //min=0 max=25 step=1
const branchHeightMax = 10; //min=0 max=25 step=1
const branchWidthMin = 2; //min=0 max=25 step=1
const branchWidthMax = 7; //min=0 max=25 step=1

const firstBranchMin = 5; //min=0 max=25 step=1
const firstBranchMax = 10; //min=0 max=25 step=1
const branchSpacingMin = 3; //min=0 max=25 step=1
const branchSpacingMax = 15; //min=0 max=25 step=1

const needleDensity = 0; //min=0 max=500 step=10
const needlesPerBunch = 10; //min=0 max=25 step=1
const centerline = 0; // min=0, max=1, step=1, (No Centerline, Centerline)
const cullTallBranches = 1; //min=0, max=1, step=1 (Leave Tall Branches, Cull Tall Branches)

function newCactus(x,y, bSpacing, bWidth, bHeight, bCull, bThickness,) {
	
	//Trunk
	var cactus = {
		x: x,
		y: y,
		height: randomRange([trunkHeightMin,trunkHeightMax]),
		thickness: trunkThickness,
		branches: [[],[]], //Left, Right
		density: needleDensity,
		bunchSize: needlesPerBunch
	}
	
	//Branches
	var branchSide = 0;
	while (branchSide <= 1) {
		var trunkOffset = randomRange([firstBranchMin,firstBranchMax]);
		
		while (trunkOffset < cactus.height) {
			var branch = {
				y: trunkOffset,
				side: branchSide,
				thickness: branchThickness,
				width: randomRange([branchWidthMin,branchWidthMax]) + ( cactus.thickness / 2 ),
				height: randomRange([branchHeightMin,branchHeightMax])
			}
			
			if ((trunkOffset + branch.height) < cactus.height || !cullTallBranches){
			    if ( branchSide == 0 ) cactus.branches[branchSide].push(branch)
                else cactus.branches[branchSide].unshift(branch)
			}
			
			trunkOffset += branch.height + randomRange([branchSpacingMin,branchSpacingMax])
		}
		
		branchSide++;
	}
	
	return cactus;
}

function drawCactus(cactus){
	
	//start at the bottom left of the trunk pointing up
	
	turtle.penup();
	turtle.jump(cactus.x - cactus.thickness / 2, cactus.y);
	turtle.seth(-90);
	turtle.pendown();
	
	for (var branch of cactus.branches[0]){
		drawBranch(cactus, branch);
	}
	
	turtle.goto(turtle.x(), cactus.y - cactus.height);
	turtle.seth(-90);
	turtle.circle(cactus.thickness/2,180);
	
	for (var branch of cactus.branches[1]){
		drawBranch(cactus, branch);
	}
	
	turtle.goto(turtle.x(),cactus.y);
	
	if (cactus.density) drawNeedles(cactus);
	
    if (cactus.centerline) {
        drawTrunkC(cactus)
    	for (var branches of cactus.branches) {
    		for (var branch of branches){
    			drawBranchC(cactus, branch)
    		}
    	}
    }
}

function drawBranch(cactus, branch) {
    if (branch.side == 0) {
    	turtle.goto(turtle.x(), cactus.y - branch.y + branch.thickness / 2 );
    	turtle.goto(cactus.x - branch.width, turtle.y());
    	turtle.seth(180);
    	turtle.circle(branch.thickness / 2,90);
    	turtle.goto(turtle.x(), turtle.y() - branch.height);
    	turtle.circle(branch.thickness/2,180);
    	turtle.goto(turtle.x(), turtle.y() + branch.height - branch.thickness /2);
    	turtle.goto(cactus.x - cactus.thickness / 2, turtle.y());
    } else {
        turtle.goto(turtle.x(), cactus.y - branch.y - branch.thickness / 2);
        turtle.goto(cactus.x + branch.width - branch.thickness/2, turtle.y());
        turtle.goto(turtle.x(), turtle.y() - branch.height + branch.thickness / 2);
        turtle.seth(-90);
        turtle.circle(branch.thickness/2,180);
        turtle.goto(turtle.x(), turtle.y() + branch.height);
        turtle.circle(branch.thickness/2, 90)
        turtle.goto(cactus.x + cactus.thickness / 2, turtle.y());
    }
}


function drawTrunkC(cactus){
	turtle.penup();
	turtle.jump(cactus.x, cactus.y);
	turtle.pendown();
	turtle.seth(-90);
	turtle.forward(cactus.height);
}

function drawBranchC(cactus, branch) {
    turtle.penup();
    turtle.jump(cactus.x, cactus.y - branch.y);
    turtle.pendown();
    turtle.setheading(180 * (1-branch.side));
    turtle.forward(branch.width);
    turtle.setheading(-90);
    turtle.forward(branch.height);
}

function drawNeedles(cactus) {
    //trunk
    drawNeedlesSegment(
        [cactus.x,cactus.y - cactus.thickness / 4],
        [cactus.x,cactus.y - cactus.height],
        270,
        cactus.thickness,
        cactus.density,
        cactus.bunchSize
        );
    
    //arms
    for(branchSide of cactus.branches){
        for(branch of branchSide) {
            var side = branch.side * 2 - 1
            drawNeedlesSegment(
                [ cactus.x + cactus.thickness / 2 * side, cactus.y - branch.y ],
                [ cactus.x + branch.width * side, cactus.y - branch.y ],
                270 - side * 90,
                branch.thickness,
                cactus.density,
                cactus.bunchSize
                );
            drawNeedlesSegment(
                [ cactus.x + branch.width * side, cactus.y - branch.y ],
                [ cactus.x + branch.width * side, cactus.y - branch.y - branch.height],
                270,
                branch.thickness,
                cactus.density,
                cactus.bunchSize
                );
        }
    }
}

function drawNeedlesSegment(start,end,direction,thickness,density,bunchSize){
    console.log(start,end,direction,thickness,density,bunchSize);
    var length = Math.hypot(start[0] - end[0], start[1] - end[1]);
    var needles = Math.floor(length * density / 100);
    var needleRange = [thickness/8,thickness/2];
    var needleLength = [thickness/4,thickness/2];
    
    while(needles >= 1) {
        var t = Math.random();
        var pos = {
            x: start[0] + t * (end[0] - start[0]),
            y: start[1] + t * (end[1] - start[1])
        };
        
        drawBunch(
            randomRange([pos.x - thickness/2, pos.x + thickness/2]),
            randomRange([pos.y - thickness/2, pos.y + thickness/2]),
            randomRange([direction-45,direction+45]),
            bunchSize,
            needleLength
            
            )
        needles--;
    }
}

function drawBunch(x,y,direction,bunchSize, needleLength) {
    
    while(bunchSize >= 1){
        
        drawNeedle(
            x,
            y,
            randomRange([direction-60,direction+60]),
            randomRange(needleLength),
            )
        
        bunchSize--;
    }
}

function drawNeedle(x,y,h,l) {
    console.log(x,y,h,l);
    turtle.jump(x,y);
    turtle.seth(h);
    turtle.pendown();
    turtle.forward(l);
    turtle.penup();
}

function randomRange(range) {
    return Math.round(Math.random() * (range[1] - range[0])) + range[0];
}

function walk(i) {
	var cactus = newCactus(
        startX + i%columns * offsetX ,             //x
        startY - Math.floor(i/columns) * offsetY,  //y
        );
		
	drawCactus(cactus);
	
    return i < (cacti -1);
}