So this was a fun test of my coding skills.
I wanted to try and generate fractal ferns using recursion and object-oriented programming.
STEP ONE
The Stem
The first step is to draw a line, then define points along that line that other stems can be created from.

STEP TWO
Defining The Leaf
I need a function that will describe the shape of a fern’s leaves. A sin wave can be used here for now

W and L define the width and height of our leaf.

STEP THREE
Drawing The Leaf
Plugging in the length of our original stem and our desired stem width we can generate new stems that match the leaf shape.

I can quickly alternate the branches using a function:
function alternate(num){
return (num % 2 == 0) ? 1 : -1;
}
this function uses a ternary operator! I just found out about them!

STEP FOUR
Recursion
We can run this on each of the new branches, creating a full leaf.


STEP FIVE
Polishing
Changing the angle from 90° to 60° already makes the leaf look a lot more natural

The next thing to do is to make a more fern-y looking function to describe the curve of the leaf. This can be done using a parametric function.

Two new variables (a and b) control the tapering on the top and bottom of the leaf. For a fern I’ll use an a value less than 0 and a high b value.


There are many more improvements I could make, but I’m happy with this for now.
LINK
FINAL PRODUCT
https://editor.p5js.org/woojinJang_/full/fZNqxPdnQ
Any key will add another recursion to the fern. Pressing enter will capture a full-resolution screenshot, which I have been using here.
The full code:
function setup() {
angleMode(DEGREES);
createCanvas(800, 800);
seed = new stem(400,700,600,180,270,60);
}
function keyPressed() {
seed.grow();
if(keyCode === 13) { saveCanvas(); }
}
function draw() {
background(200);
seed.drawself();
}
function fern_width(x, len, wid, a=1, b=1){
let numer = ((x / len) ** a) * ((1 - x / len) ** b);
let denom = ((a / (a+b)) ** a) * ((b / (a+b)) ** b);
return wid * numer / denom;
}
function alternate(num){
return (num % 2 == 0) ? 1 : -1;
}
class stem {
constructor(x,y,length,wid,angle,col){
this.col = col;
this.x = x;
this.y = y;
this.length = length;
this.angle = angle;
this.leaves = [];
}
grow(){
print('growing');
let gap = this.length / 16;
let cutoff = 1;
if(this.leaves.length == 0){
for(let i = 0; i*gap < this.length; i++){
let newstemx = this.x+((i*gap)*cos(this.angle));
let newstemy = this.y+((i*gap)*sin(this.angle));
let newstemlength = fern_width((i*gap),this.length,this.length*0.3,0.2,1.4);
let newstemangle = (this.angle+(60*alternate(i)));
if(newstemlength > cutoff){
this.leaves[i] = new stem(newstemx,newstemy,newstemlength,this.length*0.3,newstemangle,this.col+24);
}else{
this.leaves[i] = -1;
}
}
}
else{
for(let i = 0; i < this.leaves.length; i++){
if(this.leaves[i] != -1){ this.leaves[i].grow(); }
}
}
}
drawself(){
let xto = this.x+(this.length*cos(this.angle));
let yto = this.y+(this.length*sin(this.angle));
stroke(90,this.col,60);
strokeWeight(4);
line(this.x,this.y,xto,yto);
for(var i = 0; i < this.leaves.length; i++){
if(this.leaves[i] != -1){
this.leaves[i].drawself();
}
}
}
}
Leave a Reply