Library providing functionalities for creating and manipulating Lindenmayer systems (L-Systems) using various parameters.
Credits: lindenmayer
Yarn:
$ yarn add toosoon-lsystem
NPM:
$ npm install toosoon-lsystem
import LSystem from 'toosoon-lsystem';
const system = new LSystem({
axiom: 'F++F++F',
productions: { F: 'F-F++F-F' },
iterations: 2
});
system.iterate();
console.log(system.getAxiomString());
// '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'
Using custom alphabets allows you to use custom strings as symbols.
const ALPHABET = ['Block', 'Line'] as const;
type A = Alphabet<(typeof ALPHABET)[number]>;
new LSystem<A>({
alphabet: [...ALPHABET],
axiom: 'Block',
productions: {
Block: 'Block-Line',
Line: 'Line++Block--Line'
}
});
Defines are especially usefull when coupled with parametric syntax.
new LSystem({
defines: {
[key]: [value]
}
});
You can set productions in two ways.
Multiple productions via constructor:
new LSystem({
productions: {
[symbol]: [production],
[symbol]: [production]
}
});
Or via the setter-methods:
// Set single production
system.setProduction([symbol], [production]);
// Set multiple productions
system.setProductions({
[symbol]: [production],
[symbol]: [production]
});
The most basic production consists of a single string, representing the result of a production.
// Each 'F' will be replacd with 'FF'
system.setProduction('F', 'FF');
To allow even more flexibility than String-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.
Equivalent to String-based productions for Object-based ones.
LSystem.setProduction('F', { successor: '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 you can use Arrays of successors besides basic Strings as production results (and axioms).
LSystem.setProduction('F', {
successor: [
{ symbol: 'F', params: [1, 2] },
{ symbol: 'F', params: [3, 4] }
]
});
To add a context-sensitive check you can add before
and after
contexts properties.
// Replace 'F' with 'FF' only if precceded by 'FA' and followed by 'A'
system.setProduction('F', {
successor: 'FF',
context: { before: 'FA', after: 'A' }
});
See also the chapter on classic syntax to learn how to write more concise context sensitive productions.
See the chapter on classic syntax to learn how to write concise parametrical productions.
You may also define a condition which has to return a boolean.
// Replace 'F' with 'FF' only if condition is `true`
myLsystem.setProduction('F', {
successor: 'FF',
condition: () => {
return condition === true;
}
});
Instead of a single successor, a stochastic L-System defines an 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('F', {
stochastic: [
{ successor: 'A', weight: 50 }, // 50% probability
{ successor: 'AB', weight: 25 }, // 25% probability
{ successor: 'A+B', weight: 25 } // 25% probability
]
});
In order to create pseudo-randomness, toosoon-utils/prng functions are used to determine stochastic outputs.
Besides Strings and Arrays, you can also define functions as successors for complete flexibilty. Each successor function has also access to an info object:
-
axiom
: Reference to the current axiom. Useful in combination with index. -
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. -
params
: Shorthand forpart.params
.
A successor function returns a valid production result. If nothing or false
is returned, the symbol will not replaced.
Usages examples:
// Replace 'F' with 'A' if it is at least at index 3 (4th position) inside the current axiom, otherwise return 'B'
system.setAxiom('FFFFFFF');
system.setProduction('F', {
successor: ({ index }) => (index >= 3 ? 'A' : 'B')
});
system.iterate(); // 'FFFFFF' results in -> 'BBBAAAA'
// Replace any occurrence of 'F' with a random amount (but max. 5) of 'F'
system.setProduction('F', {
successor: () => {
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'
system.setProduction('F', {
successor: () => {
const day = new Date().getDay();
if (day === 1) return 'FM';
if (day === 2) return 'FT';
}
});
To apply your productions onto the axiom you call iterate()
on your L-System instance.
In each iteration step, all symbols of the axiom are replaced with new symbols based on your defined productions.
When you call iterate()
, the resulted axiom of your L-System is returned. You can also get the resulted axiom via getAxiomString()
.
To visualize or post-process your L-System you can define commands functions for each symbol. These functions are similar to productions, but instead of replacing the existing axiom, commands are used to draw for example different lines for different symbols. All commands are executed by calling run()
.
A very common application for commands would be the creation of turtle graphics.
// Instead of:
system.setProduction('F', {
successor: 'G',
context: { before: 'AB', after: 'C' }
});
// You can write:
system.setProduction('AB<F>C', 'G');
// Instead of:
system.setProduction('F', {
successor: [
{ symbol: 'A', params: [1] },
{ symbol: 'B', params: [2, 1] }
]
});
// You can write:
system.setProduction('F', 'A(1) B(2, 1)');
// Then use parametrical productions:
system.setProduction('A(t)', 'B(t, t + 1)');
system.setProduction('B(t, d)', 'A(t * d)');
See full documentation here.
MIT License, see LICENSE for details.