L-system trial code made with p5.js

Drawing with the L-system.

I was encouraged to try drawing some shapes with the L-system by the article by Gin (@gin_graphic) san.

【p5.js】L-systemで木を描く - ギンの備忘録

And I was more interested in the geometric shape than in imitating the natural trees.

The geometric shape with the L-system.

Drawing with the L-system can vary depending on the combination of parameters. And it is hard for me to predict the result by the parameters.

I've created the L-system trial code in p5.js to find the attractive drawing result out of such an infinite number of combinations.

👉 この記事は日本語でも読めます。

 

Totally different results just by changing the angle

I was surprised by the results when I changed the angle value in the code of the article by Gin-san as I mentioned before.


 let distance = 20;
 let angle = radians(90);

The geometric shape with the L-system.



 let distance = 20;
 let angle = radians(120);

The geometric shape with the L-system.


When I changed the production rule, it drew a different geometrical shape. These geometrical shapes attract me. The combination of the production rule, the number of recursions, and the angle to draw the branch create various result shapes.

Well, it would allow me to play endlessly.

 

L-system trial code

I've written 'L-system trial code' in p5.js. It'll help me to find the combination of the production rule, the number of recursions, and the angle to draw the branch that create attractive shape.

You can see the drawing result and the combination of the parameters on the screen. The Up-Down arrow key changes the number of recursions. The Left-Right arrow key changes the angle to draw the branch.

The production rule is important, and I don't want to lose it. So I decided to write it on the code.

The organic shape by L-system trial code.

/** 
 * L-system tester.
 * ref. https://gin-graphic.hatenablog.com/entry/2022/12/20/000000
 * 
 * @author @deconbatch
 * @version 0.1
 * @license GPL3
 * p5.js 1.5.0
 * created 2022.12.31
 */

const initProduction = '+F[+F]F';
const maxRepeat = 10;
const maxAngle = 180;
const w = 800;
const h = w;

let repeatNum = 2;
let rotateAngle = 15;

/** 
 * keyPressed : change parameters then re-draw.
 */
function keyPressed() {
  if (keyCode == UP_ARROW) {
    repeatNum++;
  } else if (keyCode == DOWN_ARROW) {
    repeatNum--;
  } else if (keyCode == RIGHT_ARROW) {
    rotateAngle += 15;
  } else if (keyCode == LEFT_ARROW) {
    rotateAngle -= 15;
  }
  repeatNum = constrain(repeatNum, 1, maxRepeat);
  rotateAngle = constrain(rotateAngle, -maxAngle, maxAngle);

  redraw();
}

/** 
 * setup : world famous setup function.
 */
function setup() {
  createCanvas(w, h)
  angleMode(DEGREES);
  noLoop();
  noFill();
  stroke(0);
}

/** 
 * draw : generate the production then draw it.
 */
function draw() {
  // generate the production
  const production = genProduction(initProduction, repeatNum);
  // fit size and position with canvas
  const fit = fitting(production, rotateAngle);
  // draw
  background(240);
  push();
  translate(fit.x, fit.y);
  strokeWeight(3 / repeatNum);
  drawShape(production, rotateAngle, fit.z);
  pop();
  drawParams(initProduction, repeatNum, rotateAngle, fit.z);
}

/** 
 * genProduction : generate the production
 */
function genProduction(_baseP, _num) {
  let word = 'F';
  for (let i = 0; i < _num; i++) {
    let replaced = word.replace(/F/g, _baseP);
    word = replaced;
  }
  return word;
}

/** 
 * fitting : calculate the size and position to fit to the canvas
 */
function fitting(_prd, _angle) {
  let len = 10; // size
  let cX = 0;   // center x
  let cY = 0;   // center y

  let minX = width;
  let minY = height;
  let maxX = 0;
  let maxY = 0;
  let x = 0;
  let y = 0;
  let a = 0;
  const saveX = [];
  const saveY = [];
  const saveA = [];
  for (let c of _prd) {
    switch (c) {
    case "F":
      x += len * sin(a);
      y += len * cos(a);
      break;
    case "+":
      a += _angle;
      break;
    case "-":
      a -= _angle;
      break;
    case "[":
      saveX.push(x);
      saveY.push(y);
      saveA.push(a);
      break;
    case "]":
      x = saveX.pop();
      y = saveY.pop();
      a = saveA.pop();
      break;
    default:
      break;
    }
    minX = min(minX, x);
    minY = min(minY, y);
    maxX = max(maxX, x);
    maxY = max(maxY, y);
  }

  const rate = max((maxX - minX) / width, (maxY - minY) / height) * 1.5;
  if (rate != 0) {
    len /= rate;
    const rminX = minX / rate;
    const rminY = minY / rate;
    const rmaxX = maxX / rate;
    const rmaxY = maxY / rate;
    cX = width * 0.5 - (rminX + (rmaxX - rminX) * 0.5);
    cY = height * 0.5 - (rminY + (rmaxY - rminY) * 0.5);
  }

  return createVector(cX, cY, len);
}

/** 
 * drawShape : draw the shape
 */
function drawShape(_prd, _angle, _len) {
  for (let c of _prd) {
    switch (c) {
    case "F":
      line(0, 0, 0, _len);
      translate(0, _len);
      break;
    case "+":
      rotate(-_angle);
      break;
    case "-":
      rotate(+_angle);
      break;
    case "[":
      push();
      break;
    case "]":
      pop();
      break;
    default:
      break;
    }
  }
}

/** 
 * drawParams : draw parameters
 */
function drawParams(_f, _n, _a, _l) {
  const siz = 12;
  push();
  noStroke();
  fill(0);
  textSize(siz);
  textAlign(LEFT, CENTER);
  textFont('monospace');
  text('PRODUCTION:' + _f, 10, siz);
  text('    REPEAT:' + _n, 10, siz * 2);
  text('     ANGLE:' + _a, 10, siz * 3);
  text('    LENGTH:' + _l, 10, siz * 4);
  pop();
}

/*
Copyright (C) 2022- deconbatch

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>
*/

Please feel free to use this example code under the terms of the GPL.

 

The combinations that I loved

Let me show you some combinations and the results.

Ghostie

I used 'blendMode(SCREEN)'.

Shape like a ghost shadow.
  • rule : -F[+F+F]-F
  • reccursion : 8
  • angle : 150 degree

 

Harvest

I combined the two results. I drew it with ellipse() and line().

The  ears of wheat by L-system.
  • rule : F[[[+FF]-F[-F]]F]-F
  • reccursion : 4
  • angle : 13 degree, -9 degree

With line() without ellipse()

The  ears of wheat after threshing.

 

Abstract painting

Angle magic.

Example abstract painting by L-system.
  • rule : F[+F]-F[+F[-F]+F]
  • reccursion : 5
  • angle : 60.1 degree, 120.1 degree

Product development.

Example abstract painting by L-system.

 

You can waste your time with L-system

You can't stop if you start with the L-system once because the little differences can lead to unexpected results.

You can waste your infinite time with my L-system trial code. It's so much fun!

 

Previous Post
No Comment
Add Comment
comment url