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);
}