• Stars
    star
    184
  • Rank 209,187 (Top 5 %)
  • Language
    JavaScript
  • License
    MIT License
  • Created about 9 years ago
  • Updated 5 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Feature complete classic L-System library (branching, context sensitive, parametric) & multi-purpose modern L-System/LSystem implementation that can take javascript functions as productions. It is not opinionated about how you do visualisations.

Lindenmayer Build Status minified gzip size

Lindenmayer is a L-System library using modern (ES6) JavaScript with focus on a concise syntax. The idea is to have a powerful but simple base functionality, that can handle most use-cases by simply allowing anonymous functions as productions, which makes it very flexible in comparison to classic L-Systems.

The library can also parse to some extent classic L-System syntax as defined in Aristid Lindenmayers original work Algorithmic Beauty of Plants from 1990. For example branches: [] or context sensitive productions: <>.

If you simply want to work with L-Systems in 3D and VR without defining your own draw methods, you can check out the accompanying aframe-lsystem-component.

Full API doc | Getting Started | A-Frame (VR) L-System component

3D Hilbert Curve rendered in Interactive L-System builder.

Examples

Install

Direct download

<script src="lindenmayer.browser.js"></script>

npm

If you would like to use npm instead of directly downloading:

npm install --save lindenmayer

Then in your Node.js script:

var LSystem = require('lindenmayer')

or via import syntax:

import LSystem from 'lindenmayer'

Or in your index.html:

<script src="node_modules/lindenmayer/dist/lindenmayer.browser.js"></script>

See releases for change logs.

Quick Intro

// Initializing a L-System that produces the Koch-curve
let kochcurve = new LSystem({
      axiom: 'F++F++F',
      productions: {'F': 'F-F++F-F'}
})
// Iterate the L-System two times and log the result.
let result = kochcurve.iterate(2)
console.log(result)
//'F-F++F-F-F-F++F-F++F-F++F-F-F-F++F-F++F-F++F-F-F-F++F-F++F
//-F++F-F-F-F++F-F++F-F++F-F-F-F++F-F++F-F++F-F-F-F++F-F'

There are multiple ways to set productions, including javascript functions:

// Directly when initializing a new L-System object:
let lsystem = new LSystem({
  axiom: 'ABC',
  productions: { 'B': 'BB' }
})

// After initialization:
lsystem.setProduction('B', 'F+F')


// Stochastic L-System:
lsystem.setProduction('B', {
  successors: [
  {weight: 50, successor: 'X'}, // 50% probability
  {weight: 25, successor: 'XB'},// 25% probability
  {weight: 25, successor: 'X+B'}// 25% probability
]})

// Context Sensitive:
lsystem.setProduction('B', {leftCtx: 'A', successor: 'B', rightCtx: 'C'})

// or if you prefer the concise *classic* syntax for context sensitive productions:
lsystem.setProduction('A<B>C', 'Z')



// You can also use ES6 arrow functions. Here a Simple (custom) stochastic production, producing `F` with 10% probability, `G` with 90%
lsystem.setProduction('B', () => (Math.random() < 0.1) ? 'F' : 'G')


//Or make use of additional info fed into production functions on runtime.
// Here: return 'B-' if 'B' is in first half of word/axiom, otherwise 'B+'
lsystem.setProduction('B', (info) => (info.currentAxiom.length / 2) <= info.index ? 'B-' : 'B+')

Documentation

The following section is a quick overview. Please refer to the full documentation for a detailed usage reference.

Initialization

let lsystem = new LSystem(options)

options may contain:

  • axiom: A String or an Array of Objects to set the initial axiom (sometimes called axiom, start or initiator).
  • productions: key-value Object to set the productions from one symbol to its axiom. Used when calling iterate(). A production can be either a String, Object or a Function.
  • finals: Optional key-value Object to set functions that should be executed each symbol in sequential order when calling final(). Useful for visualization.

advanced options (see API docs for details):

  • branchSymbols: A String of two characters. Only used when working with classic context sensitive productions. The first symbol is treated as start of a branch, the last symbol as end of a branch. (default: "[]", but only when using classic CS syntax)
  • ignoredSymbols: A String of characters to ignore when using context sensitive productions. (default: "+-&^/|\\", but only when using classic CS syntax)

Most often you will find yourself only setting axiom, productions and finals.

Setting an Axiom

As seen in the first section you can simply set your axiom when you init your L-System.

let lsystem = new LSystem({
      axiom: 'F++F++F'
})

You can also set an axiom after initialization:

let lsystem = new LSystem({
      axiom: 'F++F++F'
})
lsystem.setAxiom('F-F-F')

Setting Productions

Productions define how the symbols of an axiom get transformed. For example, if you want all As to be replaced by B in your axiom, you could construct the following production:

let lsystem = new LSystem({
  axiom: 'ABC',
  productions: {'A': 'B'}
})
//lsystem.iterate() === 'BBC'

You can set as many productions on initialization as you like:

let lsystem = new LSystem({
      axiom: 'ABC',
      productions: {
        'A': 'A+',
        'B': 'BA',
        'C': 'ABC'
      }
})
// lsystem.iterate() === 'A+BAABC'

You could also start with an empty L-System object, and use setAxiom() and setProduction() to edit the L-System later:

let lsystem = new LSystem()
lsystem.setAxiom('ABC')
lsystem.setProduction('A', 'AAB')
lsystem.setProduction('B', 'CB')

This can be useful if you want to dynamically generate and edit L-Systems. For example, you might have a UI, where the user can add new production via a text box.

A major feature of this library is the possibility to use functions as productions, which could be used for stochastic L-Systems:

// This L-System produces `F+` with a 70% probability and `F-` with 30% probability
let lsystem = new LSystem({
      axiom: 'F++F++F',
      productions: {'F': () => (Math.random() <= 0.7) ? 'F+' : 'F-'}
})

// Productions can also be changed later:
lsys.setProduction('F', () => (Math.random() < 0.2) ? 'F-F++F-F' : 'F+F')

If you are using functions as productions, your function can make use of a number of additional parameters that are passed as an info object to the function (see full docs for more details):

lsys.setAxiom('FFFFF')
lsys.setProduction('F', (info) => {
  // Use the `index` to determine where inside the current axiom, the function is applied on.
  if(info.index === 2) return 'X';
})
// lsys.iterate() === FFXFF

The info object includes:

  • index: the index inside the axiom
  • currentAxiom: the current full axiom/word
  • part: the current part (symbol or object) the production is applied on. This is especially useful if you are using parametric L-Systems (see last chapter) to have access to parameters of a symbol.

For a shorter notation you could use the ES6 feature of object destructuring (has support in most modern browsers):

lsys.setProduction('F', ({index}) => index === 2  ? 'X' : false);

If undefined or false is returned in a production function, as above, the initiating symbol or symbol object is returned (in aboves example, that would be'F').

Getting Results

Now that we have set up our L-System set, we want to generate new axioms with iterate():

// Iterate once
lsystem.iterate();

// Iterate n-times
lsystem.iterate(5);

iterate() conveniently returns the resulting string:

console.log(lsystem.iterate())

If you want to fetch the result later, use getString():

lsystem.iterate()
console.log(lsystem.getString())

Putting it all together

Final functions: Visualization and other post processing

Most likely you want to visualize or post-process your L-Systems output in some way. You could iterate and parse the result yourself, however lindemayer already offers an easy way to define such postprocessing: final functions. In those final functions you can define what should be done for each literal/character. The classic way to use L-Systems is to visualize axioms with turtle graphics. The standard rules, found in Aristid Lindenmayer's and Przemyslaw Prusinkiewicz's classic work Algorithmic Beauty of Plants can be easily implented this way, to output the fractals onto a Canvas.

You can fiddle with the following example in this codepen!

<body>
	<canvas id="canvas" width="1000" height="1000"></canvas>
</body>
var canvas = document.getElementById('canvas')
var ctx = canvas.getContext("2d")

// translate to center of canvas
ctx.translate(canvas.width / 2, canvas.height / 4)

// initialize a koch curve L-System that uses final functions
// to draw the fractal onto a Canvas element.
// F: draw a line with length relative to the current iteration (half the previous length for each step)
//    and translates the current position to the end of the line
// +: rotates the canvas 60 degree
// -: rotates the canvas -60 degree

var koch = new LSystem({
  axiom: 'F++F++F',
  productions: {'F': 'F-F++F-F'},
  finals: {
    '+': () => { ctx.rotate((Math.PI/180) * 60) },
    '-': () => { ctx.rotate((Math.PI/180) * -60) },
    'F': () => {
      ctx.beginPath()
      ctx.moveTo(0,0)
      ctx.lineTo(0, 40/(koch.iterations + 1))
      ctx.stroke()
      ctx.translate(0, 40/(koch.iterations + 1))}
   }
})

koch.iterate(3)
koch.final()

And the result:

Resulting image

As this library is not opinionated about what your results should be like, you can write your own finals. Therefore you can draw 2D turtle graphics as seen above, but also 3D ones with WebGL/three.js, or even do other things like creating sound!

Advanced Usage

Parametric L-Systems

When defining axioms you may also use an Array of Objects instead of basic Strings. This makes your L-System very flexible because you can inject custom parameters into your symbols. Eg. a symbol like a A may contain a food variable to simulate organic growth:

let parametricLsystem = new lsys.LSystem({
  axiom: [
    {symbol: 'A', food:0.5},
    {symbol: 'B'},
    {symbol: 'A', , food:0.1},
    {symbol: 'C'}
  ],
  // And then do stuff with those custom parameters in productions:
  productions: {
    'A': ({part, index}) => {
      // split A into one A and a new B if it ate enough:
      if(part.food >= 1.0) {
        return [{symbol: 'A', food:0}, {symbol: 'B', food:0}]
      } else {
        // otherwise eat a random amount of food
        part.food += Math.random() * 0.1;
        return part;
      }
    }
  }
});

// parametricLsystem.iterate(60);
// Depending on randomness:
// parametricLsystem.getString() ~= 'ABBBBBABBBC';
// The first part of B's has more B's because the first A got more initial food which in the end made a small difference, as you can see.

As you can see above, you need to explicitly define the symbol value, so the correct production can be applied.

Full Documentation

More Repositories

1

pen-paper-coffee-syntax

Light Atom syntax theme designed for writing texts in markdown.
Less
56
star
2

aframe-lsystem-component

L-System/LSystem component for A-Frame to draw 3D turtle graphics. Using Lindenmayer as backend.
JavaScript
39
star
3

aframe-fit-texture-component

A fit-texture component for A-Frame VR.
JavaScript
24
star
4

colorStripes

Create colorful striped images to use as desktop backgrounds or just for staring at the colors change when interacting with the sketch. ;)
JavaScript
10
star
5

fit-bezier-3d-visualization

Compare a 3D polyline with a 3D bezier spline in one space
JavaScript
8
star
6

pen-paper-coffee-vscode

Light Visual Studio Code color theme designed for writing texts in markdown and as a code all-rounder
5
star
7

a-vue

aframe<->vuejs integration test
HTML
3
star
8

CamColorExtruder

Transform pixels from your webcam into rotating and shaded cubes. Using your webcam it identifies areas of certain color and extrudes them as 3D-Boxes. You can switch colors by pressing 'r', 'g', 'b'. Pressing 'x' enables custom color picking: drag/click with your mouse at color you'd like to extrude.
Makefile
3
star
9

PartySim

A Party Simulation
JavaScript
2
star
10

neonion-rest-js

Accessing the neonion-rest proposal from the browser.
JavaScript
1
star
11

EllipticalLife

an automation game - inspired by Game of Life
Processing
1
star
12

GPN17-kugelspiel

A balance game for the GPN17 badge
Arduino
1
star
13

Arducam-Obscura

An arduino, light sensor and motor controlled pinhole camera project. This is the arduino code.
Arduino
1
star
14

Pinhole-Exposure-Calculator

Exposure calculator for (pinhole) cameras
Nim
1
star
15

handgui

Tagging and capturing physical objects with a beamer and the LeapMotion controller.
Processing
1
star