Y0/FMCC/W1 – Ferns

So this was a fun test of my coding skills.

I wanted to try and generate fractal ferns using recursion and object-oriented programming.

The first step is to draw a line, then define points along that line that other stems can be created from.

Made using p5.js

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.

Made using Desmos Graphing Calculator

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

Made using p5.js

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!

Made using p5.js

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

Second recursion with p5.js
Third recursion with p5.js

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

Made using p5.js

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.

via ChatGPT

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.

Made using Desmos Graphing Calculator
Made using p5.js

There are many more improvements I could make, but I’m happy with this for now.

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

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *