Giter VIP home page Giter VIP logo

nylki / lindenmayer Goto Github PK

View Code? Open in Web Editor NEW
179.0 9.0 14.0 941 KB

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.

License: MIT License

JavaScript 97.72% HTML 2.28%
turtle-graphics l-systems lindenmayer lsystem fractal

lindenmayer's Introduction

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

lindenmayer's People

Contributors

dependabot[bot] avatar multimeric avatar nylki avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

lindenmayer's Issues

Add possibility to call final functions in last iteration step

It might be useful to allow calling final functions when doing the last iteration. If you want to visualize/etc. it anyways directly after iteration. This would save looping the whole string a second time for visualizing and may improve rendering performance!?

However, it's orobably incompatible with webworkers for a while (if the final functions draw on a canvas) and won't work as expected if you use branches or context-sensitive checks.

Should we have built-in support for conditional productions, stochastic productions?

Hi! You've done fantastic work on this library so far. However I've been reading through ABOP and I wonder if the API could support some of the standard notational things in the book.

For example, stochastic productions and conditional productions seem very common, so could we have additional keys on the production object for these? For example:

lsys.setProduction('F', 'F-F++F-F', {chance: 0.3, condition: () => time > 3})

How to render tree leaves on a second pass, so they overlay branches?

Hey! This is a kickass package, thanks for putting it together!

I'm trying to add leaves to trees, but the issue is that branches that are drawn later in the process overlay previously rendered leaves. I'd like to render all the leaves on TOP of the branches, like in a second pass, but I can't figure out how to do that. Any ideas?

Screenshot illustrating the issue:
https://www.dropbox.com/s/3im42474nikycsy/Screenshot%202016-06-18%2001.16.07.png?dl=0

You'll notice it most in the right-most tree in that image. Will post code in next reply.

How to have a production with multiple parameterised inputs

I want to create a production +F => S where both + and F are parameterised, so they are actually {symbol: '+', value: 90} and {symbol: 'F', value: 1}. Or, in classical notation, +(a)F(b) => S(c). How do I make a production whose input is two parameterised symbols?

I expected this to work, but the production was never used, even though I know I had the sequence +F in my inputs.

    productions: {
        '+F': ({part}) => {
            return [{
                symbol: 'C', value: part.value
            }]
        }
    }

improve documentation

  • mention new features implemented in #16 in readme.md

also:

  • better make use of jekyll
  • have full API docs (in works) and some hands-on/getting started stuff (in works)

transformClassicCSProduction being passed two arguments but only accepting one

On line 52 of lindenmayer.js we see the following:

if(this.allowClassicSyntax === true) {
        newProduction = transformClassicCSProduction(newProduction, this.ignoredSymbols);
}

Yet the function transformClassicCSProduction seems to take only one argument in transformersClassicSyntax.js.

export function transformClassicCSProduction (p) {

  // before continuing, check if classic syntax actually there
  // example: p = ['A<B>C', 'Z']

  // left should be ['A', 'B']
  let left = p[0].match(/(.+)<(.)/);

  // right should be ['B', 'C']
  let right = p[0].match(/(.)>(.+)/);

  // Not a CS-Production (no '<' or '>'),
  //return original production.
  if(left === null && right === null) {
    return p;
  }

Is this intentional? I'm trying to model Tree B on page 43 of this book by Przemyslaw Prusinkiewicz and James Hanan with the respective L-system on the previous page (pg. 42) using your library but I believe there's a problem with how ignored symbols are passed.

help wanted: audit/improve CS-matching function

Although the library performs properly in all cases I tested previously, I am not 100% sure wether the matching algorithm for context sensitive productions @ https://github.com/nylki/lindenmayer/blob/master/lindenmayer.js#L314 works as expected and as defined in "The Algorithmic Beauty of Plants".

If you have experience with (context-sensitive) pattern matching, and branching syntax, I'd very much appreciate if you could take a look at the match() function and see if it performs as expected or even improve performance. I positive that the algorithm could be improved upon.

I am especially unsure whether the algorithm performs properly in complex branching situation. Simple regex does not work here unfortunately, because of branching situations.

There are several tests that can be run via npm test that should pass, but new, more comprehensive tests would probably be a good idea too. It is also not excluded that some tests might be flawed, taking a look there might be a good idea too.

Make LSystem an overrideable ES6 class

I was attempting to use the LSystem class as follows

import LSystem from 'lindenmayer'

export class SimpleTreeSystem extends LSystem {
    //Override the parent constructor to use a specific system
    constructor() {
        super({
            axiom: ['X'],
            productions: {
                X: () => [
                    {symbol: 'F', value: 1},
                    '[',
                    {symbol: '+', value: 20},
                    'X',
                    ']',
                    {symbol: 'F', value: 1},
                    '[',
                    {symbol: '+', value: -20},
                    'X',
                    ']',
                    {symbol: '+', value: 20},
                    'X'
                ],
                F: ({part}) => [{symbol: 'F', value: part.value * 2}]
            }
        })
    }

    /**
     * Override the normal iterate method so that by default it iterates 7 times
     * @param {number} iterations The number of times to iterate the L-System
     */
    iterate(iterations){
        const n = 7
        return super.iterate(iterations || n)
    }
}

However since the LSystem function simply sets method properties on the object instead of its prototype, that means that my custom iterate implementation is overwritten by the LSystem implementation.

Could the LSystem function be rewritten to a class?

How to make this library work in Internet Explorer?

Thanks for making this great library!

I created a website about fractals and used your library to generate L-System rules for various fractal types. Lately the website has gotten very popular and there are thousands of Internet Explorer uses who use it. Unfortunately as your library uses the latest JavaScript tricks, it doesn't work in Internet Explorer.

Is there any way to make use this library in Internet Explorer?

More diverse examples

  • Better L-System-Builder with automatic switch to 3D when using 3D symbols
  • more from Algorithmic Beauty of Plants, for example: complex tree modelling, flowers, time lapse growth simulation
  • other than turtle graphics
  • sound, music
  • GLSL Shader

add option to treat all production lists stochastically

We could support the option that all productions that have lists, are treated like classic stochastic productions: if there are three possible productions in the list, the chance is 0.333 for each of them. But with a guarantee that one is performed. This equals the transformClassicStochasticProductions function.

This would make it easier for users to do text book examples.

benchmark L-Systems that are functional equal but differ in data types

  • Try wether transformed production functions (like classicCS -> function) are slower/faster than having the function inlined possibly inlined in getProductionResult, because it could save us the function call overhead for each symbol, but would clutter the code somewhat
  • compare speed between functional equal LSystems and compare performance based on number of iterations to see if some performe better in certain situations. For example test the performance of the following three functional identical systems:
let lsys1 = new LSystem({
  axiom: 'FF',
  productions: {
    'F': 'FFF'
  }
});

let lsys2 = new LSystem({
  axiom: [{symbol: 'F'}, {symbol: 'F'}],
  productions: {
    'F': [{symbol: 'F'}, {symbol: 'F'}, {symbol: 'F'}]
  }
});

let lsys3 = new LSystem({
  axiom: [{symbol: 'F'}, {symbol: 'F'}],
  productions: {
    'F': () => [{symbol: 'F'}, {symbol: 'F'}, {symbol: 'F'}]
  }
});
  • benchmark memory comparing String based LSystem and object based ones

match() should work for objects and return matched object params

Returning matched objects in context sensitive L-Systems is useful if you want to modify those (neighboring) objects.
For example nutrients trickling up a tree: you may check if a leaf L has a branch F via F<L. That L could the syphon some virtual F.water of that into itself (L.water) for example, to simulate nutrition flow.

multiple productions on same symbol: object order not preserved

lets say you have:

axiom: 'ABC'
productions: {
  'B>C': 'X',
  'A<B': 'Y',
  'B': 'Z'
}

This is currently allowed. (Internally a successors: [] is created).

All three productions on 'B' meet the context sensitive condition.
You would expect to have 'X' returned, because it is the first in the list.
This is however not guaranteed to be the case, because object property order is not guaranteed to be preserved! See eg: http://stackoverflow.com/questions/5525795/does-javascript-guarantee-object-property-order/38218582#38218582

This is an edge-gase and generally no problem for most L-Systems, but could still cause problems.

Solution

  • Recommended: use object-based productions like: successors: [{leftCtx: ...}, {leftCtx: ....}], because in that case, the order is preserved by the successors array.

  • setting productions via setProduction() in your prefered order

  • alternatively: change library. switch/allow to tuple-arrays in productions

add index (etc.) parameter to final function

This is not only useful for productions but also final functions (eg bezier curves that use the next points location for determine a control point for example when rendering)

multiple classic context sensitive productions

This does not work yet, as the productions map, maps to the symbol. Eg: F>F, F and A<F>C would all be set via productions.set('F', โ€ฆ). But we would probably be better of with appending to a list, that then gets evaluated sequencially, as is already supported if you set a list as production.
So lsys.setProduction('F>F', 'FF) should test if lsys.productions.has('F'), if so, lsys.productions.get('F'), append if it is a list, otherwise create a list, add previous and new production to it.

This should work:

  it('multiple CS production on the same base symbol should work.', function () {
    let cs_LSystemMulti = new LSystem({
          axiom: 'ABCDEFGHI',
          productions: {
            'B<B>C': 'X',
            'A<B>C': 'Y',
            'A<B>D': 'Z',
        }
        });
    expect(cs_LSystemMulti.iterate()).to.equal('AYCDEFGHI');


  });

auto-conversion strings/list

Idea: If axiom is a string: just use string operations (because they are faster), but once a production tries to return an object, transform axiom into list of objects so it can work without errors.

Vice versa, when the axiom is a list of objects, any productions result that is only strings would be auto-transformed into list of objects.

This makes it possible to write shorter productions in productions where you eg. don't need parameters, while maintaining flexibility when you want to use a more complex (context sensitive, parametric) production.

Instead of writing

let lsystem = new LSystem({
	axiom: [{symbol: 'A'}, {symbol: 'B', myParameter: 23}, {symbol: 'C'}],
	productions: {
		'A': [{symbol: 'B'}, {symbol: 'A'}, {symbol: 'B'}, {symbol: 'A'}, {symbol: 'A'}]
		'B': ({part, myParameter}) => {return (myParameter === 23) ? {symbol: 'C'} : part},
		'C': [{symbol: 'B'}, {symbol: 'A'}]
	}
})

You could write:

let lsystem = new LSystem({
	axiom: [{symbol: 'B'}, {symbol: 'B', myParameter: 23}, {symbol: 'C'}],
	productions: {
		'A': 'BABAA'
		'B': ({part, myParameter}) => {return (myParameter === 23) ? {symbol: 'C'} : part},
		'C': 'AB'
	}
})

Should then also mixed iterables be allowed? like:

'B': ['BC', {symbol: 'B', myParameter: 23}, 'C']

Or would it create too much oerhead in general?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.