let myLsystem = new LSystem([options])
Options
-
axiom
: The initial word/string of your L-System. Sometimes also called initiator. This can be either a String like'ABC'
or an Array of Objects in the form:[{symbol: 'A'}, {symbol: B}, {symbol: C}]
. The axiom can also bet set later viasetAxiom()
. -
productions
: Key-value Object to set the productions from one symbol to its axiom. Applied when callingiterate()
. A production can either be a String, Object, Array of Objects or a Function. More on productions further below. -
finals
: Optional key-value Object to set Functions be executed for each symbol in sequential order. Useful for visualization. Used when callingfinal(<optional> arg)
. Each final function is of the form:(info, arg) => {}
info
:{index, part}
arg
: the optional argument supplied withfinal
. Useful for referencing render targets for example.
-
allowClassicSyntax
: Toggles support for classic syntax features that often are more concise and mirror examples in the book better but are also less flexible. At the moment only classic context sensitive productions is toggled by this flag (eg:A<B>C
). See the chapter on classic syntax features for more info. default:true
. -
branchSymbols
: A String tuple of the symbols treated as branches. default:'[]'
-
ignoredSymbols
: A String of symbols to ignore when performing context sensitive checks. Eg. you may want to define+-
in two-dimensional turtle-graphic L-Systems to be ignored. default:'+-&^/|\\'
. -
forceObjects
: Toggles automatic conversion of Strings into Objects. Eg. axiom'AB'
gets converted to[{symbol: 'A'}, {symbol: 'B'}]
. default:false
.
let myLsystem = new LSystem({
axiom: 'F',
productions: {
'F': 'F-A'
'A': 'FF++FA'
}
})
let myLsystem = new LSystem({
axiom: 'ABCDE',
productions: {
'A': 'A+AAC',
'B': {leftCtx: 'A', successor: 'A'},
'C': {rightCtx: 'D', successor: 'ABCD'}
}
})
This one is semantically equivalent to the previous example, but uses the classic syntax which is supported by default (support can be turned of by setting allowClassicSyntax:false
).
let myLsystem = new LSystem({
axiom: 'ABCDE',
productions: {
'A' : 'A+AAC',
'A<B' : 'A',
'C>DE' : 'ABCD'
}
})
You can set productions in two ways.
Multiple productions via constructor:
let myLsystem = new LSystem({
productions: {
[symbol]: [production],
[symbol]: [production]
}
})
Or via their setter-methods:
// Set single production
myLsystem.setProduction([symbol], [production])
// set multiple productions
myLsystem.setProductions({
[symbol]: [production],
[symbol]: [production]
})
-
symbol
: A one symbol String, eg. 'F'. -
production
: Either the result of a production (String, Array), a production Object or a production Function. How a production can exactly look like will be explained below.
Productions in lindenmayer.js come in different flavours suited for different situations:
The most basic production consists of a single String, representing the result of a production.
// Each F will be replacd with FF
myLsystem.setProduction('F', 'FF');
If you are reading about L-System in the classic ABOP, you may have stumbled upon parametric L-Systems. Those have optional parameters inside each symbol. To make this possible using Lindenmayer.js, you can use Arrays of Objects {symbol, [custom parameters]}
besides basic Strings as production results (and axioms).
// Each F will be replaced with FF
myLsystem.setProduction('F', [{symbol: 'F'}, {symbol: 'F'}]);
// Or the same but with additional parameters per symbol.
myLsystem.setProduction('F', [
{symbol: 'F', params: [5,6,1]},
{symbol: 'F', params: [1,0,0]]}
]);
You can define any number of custom parameters (NOTE: Each symbol object must always posses a symbol
property! ):
myLsystem.setAxiom([{symbol: 'F', food: 10, size: 4}])
myLsystem.setProduction('F', [
{symbol: 'A', food: 5, size: 2, color: 'rgb(255, 0, 0)'},
{symbol: '+'},
{symbol: 'A', food: 5, size: 2, color: 'rgb(0, 255, 0)' }
]);
If you want to learn more about parametric L-Systems, the following chapters will give you some more details.
myLsystem.setProduction( {successor/successors: [String, Array, Function]/[Array] , leftCtx: [String], rightCtx:[String], condition: [Function]} )
To allow even more flexibility than String or Array based productions, you can choose to use a wrapper Object in the following way to allow for stochastic, context-sensitive and conditional L-Systems.
This object basically wraps around a regular Array, String or Function Production, which are now defined in the successor
field. The additional functionality can be used via the leftCtx
, rightCtx
, successors
and condition
properties.
A barebone production using such a wrapper Object:
// Instead of:
myLsystem.setProduction('F', 'FF');
// You would write:
myLsystem.setProduction( 'F', {successor: 'FF'} );
// Or with an Array as successor/production result.
myLsystem.setProduction('F', { successor: [{symbol: 'F'}, {symbol: 'F'}] });
The above example do not yet make use of those extra functionality. To add eg. a context-sensitive check you could rewrite the second one to:
// You would write:
myLsystem.setProduction('F', { successor: 'FF', leftCtx: 'FB' });
Those extra properties are explained in more detail in the following short chapters.
// Replace 'F' with FF only if left part is FX and the right part is 'X'
myLsystem.setProduction('F', {successor: 'FF', leftCtx: 'FX', rightCtx: 'X'});
See also the chapter on classic syntax to learn how to write more concise context sensitive productions.
You may also define a condition
which has to return a boolean:
// Replace 'F' with FF only if it is monday (yeah, probably less useful in practice ;), but that should illustrate what potential for creativity you have with.)
myLsystem.setProduction('F',
{successor: 'FF', condition: () => new Date().getDay() === 1});
Instead of a single successor
, a stochastic L-System defines a successors
array which includes multiple objects with their own successor
. The weight
property defines the probability of each successor to be choosen. If all successors have the same weight they have an equal chance to get choosen. If one successor has a higher weight than another, it is more likely to get choosen.
lsystem.setProduction('B', {
successors: [
{weight: 50, successor: 'X'}, // 50% probability
{weight: 25, successor: 'XB'},// 25% probability
{weight: 25, successor: 'X+B'}// 25% probability
]})
Besides Strings, Arrays and (wrapping) Objects you can also define functions as productions for complete flexibilty. Each production function has also access to an info object.
myLsystem.setProduction([symbol], [Function(info)])
info object:
-
index
: The current index of the symbol inside the whole axiom. -
part
: The current symbol part. Not very useful for String based L-Systems. But for Array based ones, this lets you access the whole symbol object, including any custom parameters you added. eg.: part = {symbol: 'F', food: 4, myCustomParameter: true, params: [1,2]} -
params
: This is a shorthand forpart.params
(see above) -
currentAxiom
: Reference to the current axiom/word. Useful in combination withindex
.
A production function returns a valid successor, like a String or Array. If nothing or false
is returned, the symbol will not replaced.
Replace 'F' with 'A' if it is at least at index 3 (4th position) inside the current axiom, otherwise return 'B':
myLsystem.setAxiom('FFFFFFF');
myLsystem.setProduction('F', ({index}) => index >= 3 ? 'A' : 'B');
myLsystem.iterate(); // FFFFFF results in -> BBBAAAA
Replace any occurrence of 'F' with a random amount (but max. 5) of 'F':
myLsystem.setProduction('F', () => {
let result = '';
let n = Math.ceil(Math.random() * 5);
for (let i = 0; i < n; i++) result += 'F';
return result;
})
Replace 'F' with 'FM' on mondays and with 'FT' on tuesdays. Otherwise nothing is returned, therefore 'F' stays 'F'.
myLsystem.setProduction('F', () => {
let day = new Date().getDay();
if (day === 1) return 'FM';
if (day === 2) return 'FT';
});
Parametric usage:
// Duplicate each F but reduce custom `size` parameter for new children by 50%.
myLsystem.setProduction('F', ({part}) =>
[{symbol: 'F', food: part.size / 2},
{symbol: 'F', food: part.size / 2}
]
);
To apply your productions onto the axiom you call iterate([n])
on your L-System object:
// iterate only once without arguments
myLsystem.iterate();
// iterate multiple times
mylsystem.iterate(5);
In each iteration step, all symbols of the axiom are replaced with new symbols based on your defined productions:
let myLsystem = new LSystem({
axiom: 'F+X-X',
ignoredSymbols: '+-',
productions: {
'F': 'G+H',
'X': {leftCtx: 'F', successor: 'YZ'}
}
});
let result = myLsystem.iterate();
console.log(result);
// result = 'G+H-YZ-X'
// Note that, because the production for 'X' is context-sensitive (leftCtx:F), only the first X is replacd by 'YZ'.
You can see more examples in the examples folder or take a look at the tests.
Basic usage:
myLsystem.iterate();
let result = myLsystem.getString();
// or:
let result = myLsystem.iterate();
When you call iterate()
, the reduced string result/axiom of your L-System is returned. You can also get the string result/axiom via getString()
.
To retrieve the raw result/axiom you can use getRaw()
or directly access the axiom
property.
To get the raw axiom may be useful if you are operating with arrays/objects in your axiom and productions. For string-based L-Systems it makes no difference.
To demonstrate the different behaviors, please take a look below:
String based L-System
let myLsystem = new LSystem({
axiom: 'F',
productions: {'F': 'F+F'}
});
// Before calling iterate()
let result = myLsystem.getString(); // result = 'F'
result = myLsystem.getRaw(); // result = 'F'
result = myLsystem.axiom; // result = 'F'
// Calling iterate()
result = myLsystem.iterate(); // result = 'F+F'
// Getting results after calling iterate()
result = myLsystem.getString(); // result = 'F+F'
result = myLsystem.getRaw(); // result = 'F+F'
result = myLsystem.axiom; // result = 'F+F'
Array based L-System
let myLsystem = new LSystem({
axiom: [{symbol: 'F'}],
productions: {'F': 'F+F'}
});
// Before calling iterate()
let result = myLsystem.getString(); // result = 'F'
result = myLsystem.getRaw(); // result = [{symbol: 'F'}]
result = myLsystem.axiom; // result = [{symbol: 'F'}]
// Calling iterate()
result = myLsystem.iterate(); // result = 'F+F'
// Getting results after calling iterate()
result = myLsystem.getString(); // result = 'F+F'
result = myLsystem.getRaw(); // result = [{symbol: 'F'}, {symbol: '+'}, {symbol: 'F'}]
result = myLsystem.axiom; // result = [{symbol: 'F'}, {symbol: '+'}, {symbol: 'F'}]
To visualize or post-process your L-System you can define final functions for each symbol. They function similar to productions, but instead of replacing the existing axiom/word, finals are used to draw for example different lines for different symbols. All finals are executed by calling lsystem.final()
.
A very common application for finals would be the creation of turtle graphics. Below is an example on how to use finals to draw turtle graphics like the Koch Snowflake on the Canvas HTML element.
You can fiddle with the following example in this codepen!
<body>
<canvas id="canvas" width="1000" height="1000"></canvas>
</body>
<script>
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()
</script>
Lindenmayer.js is not opinionated on what you do with your L-System, so you can draw 2D turtle graphics like above, but may also draw 3D ones or even do entirely different things, like creating sound and music, simply by defining your own final
functions.
Currently supported:
-
Classic syntax for parametric L-Systems. Please use the libraries native implementation for parametric L-Systems instead (consult chapter on Array-Based Productions and Function-Based Productions
-
Classic syntax for stochastic productions. Please use the libraries native implementation for stochastic L-Systems instead.
-
Classic syntax for context sensitive productions. Can be used in the following way:
// instead of:
myLsystem.setProduction('B', {leftCtx: 'A', rightCtx: 'C', successor: 'X'}
// you can write the following production if `allowClassicSyntax` is set to `true`:
myLsystem.setProduction('A<B>C', 'X')