KalyanChakravarthy.net

Thoughts, stories and ideas.

Fractal Fun and l-systems

Fractals are fun to understand and also to code. They are also philosophically significant as they offer infinite scope for dissecting an given entity, event in life or for the matter of fact the very human nature, as its never ending, but thats subjective.

L-Systems are generating self similar fractals, in which an initial set of generating rules cumulatively applied on an initial state for given 'n' generations produces a growing fractal. The production rules are a variant of formal grammar, very logical and simple to understand and create. Its concisely explained with examples in this wikipedia article on l-systems.

Couple of days back, did an impromptu code sprint and wrote some code for the lsystem-js lib project started by @cmdr2. The Javascript lib can be used to define production or fractal generation rules, handlers for variables and visually draw it on canvas (working on the last part to integrate into the lib itself).

The library is very simple to use, and has helped me to understand few fractal objects as simple as Sierpinski Triangle which for some i have never been able to push into my mind

Here is an lsystem-js lib example to generate a Fractal Plant

Variables :
X, F

Generation rules :
X -> F-[[X]+X]+F[+FX]-X
F -> FF

Action
F : Move forward
+: Turn 25 right
- : Turn 25 left
[ : preserve current state
] : retrieve state
X : do nothing

Number of generations
5

Code fragment to generate the fractal

// New lSystem - fractal/tree
frac = new lSystem.LSystem ( 'X', {'X' : 'F-[[X]+X]+F[+FX]-X', 'F' : 'FF'} );
// Generate (generation count=5)
frac.generate(5);
// get context
ctx = document.getElementById('canvas').getContext('2d');
/**
 * Fractal RunHandler([init defaults])
 * Run handler lets one define event handlers for variables
 * Here we initialize the variables required by run handler
 */
hfrac = new lSystem.LRunHandler({
    a:40,   // angle
    x:10,   // x
    y:10,   // y
    r:5,    // radius
    raMap:{},   // radius-angle map/cache
    stack:[]    // stack for tree
});
/**
 * Specify conditions. State functions
 */
hfrac.on( 'F', function() {
    ctx.beginPath();
    ctx.moveTo( this.x, this.y );
    // cache angle values
    // If the angles are repeated(highly likely),
    // it will reuse existing calculations
    // to preserve the decimal approximation.
    hash = this.r + '#' + this.a;
    if( typeof this.raMap[hash] == 'undefined' ) {
        this.raMap[hash] = {
            x : Math.round( this.r * Math.cos( this.a * Math.PI/180 ) ),
            y : Math.round( this.r * Math.sin( this.a * Math.PI/180 ) )
        };
    }
    this.x += this.raMap[hash].x;
    this.y += this.raMap[hash].y;
    ctx.lineTo( this.x, this.y );
    ctx.stroke();
});
// Turn right 25
hfrac.on( '+', function() { this.a += 25; });
// turn left 25
hfrac.on( '-', function() { this.a -= 25; });
// push the state into a stack
hfrac.on( '[', function() { this.stack.push({ x:this.x, y:this.y, a:this.a }); });
// pop the state from the stack
hfrac.on( ']', function() {
    var ls = this.stack.pop();
    this.x = ls.x;
    this.y = ls.y;
    this.a = ls.a;
});
/**
 * frac.run(arg) -> arg is a function(token) or
 *                 -> arg is an LRunHandler() Instance
 */
frac.run(hfrac);

Simple? More to come