L-System

I adapted my small L-System Parser to turtletoy.

Half of the time, it is shows a tree. The other half, it shows a snowflake.

#lsystem #fractal

Log in to post a comment.

var turtle = new Turtle();
turtle.radians();

var PI = Math.PI;
var width = 1024;
var height = 1024;

var ANGLE;
var START;
var RULES = {};
var ITERATIONS;
var turtle_length;
var turtle_scale;
var turtle_scale_factor = 1.5;


if(Math.random() < 0.5){
    // Half of the time, render snowflake
    ANGLE = 36/360*PI*2;
    START = 'F++F++F++F++F';
    RULES = {
        'F': 'F++F++F|F-F++F',
    };
    ITERATIONS = 5;
    turtle_length = 0.2;
    turtle_scale = 2.3;
    turtle.up();
    turtle.goto(-30,30);
    turtle.down();
} else {
    ANGLE = 40/360*PI*2;
    START = 'FX';
    RULES = {
        'X': '>[-FX]+FX',
    };
    ITERATIONS = 8;
    turtle_length = 40;
    turtle_scale = 0.9;
    turtle.left(PI/2);
    turtle.up();
    turtle.goto(0,40);
    turtle.down();
}

var stack = [];


var alphabet = {
    '+': function(){
    	// turn left
        turtle.left(ANGLE);
    },
    '-': function(){
        // turn right
        turtle.right(ANGLE);
    },
    'F': function(){
        // go forward
        turtle.forward(turtle_scale * turtle_length);    },
    '0': function(){
    },
    '1': function(){
        // go forward
        turtle.forward(turtle_scale * turtle_length);
    },
    'X': function(){
        // do nothing
    },
    'Y': function(){
        // do nothing
    },
    '[': function(){
        stack.push({
            x: turtle.x(),
            y: turtle.y(),
            heading: turtle.heading(),
            turtle_scale: turtle_scale,
        });
    },
    ']': function(){
        state = stack.pop();
        turtle.up();
        turtle.goto(state.x, state.y);
        turtle.setheading(state.heading);
        turtle.down();
        turtle_scale = state.turtle_scale;
    },
    'A': function() {},
    'B': function() {},
    'M': function() {turtle.forward(turtle_scale * turtle_length);},
    'N': function() {turtle.forward(turtle_scale * turtle_length);},
    'O': function() {turtle.forward(turtle_scale * turtle_length);},
    'P': function() {turtle.forward(turtle_scale * turtle_length);},
    '<': function() {turtle_scale *= turtle_scale_factor;},
    '>': function() {turtle_scale /= turtle_scale_factor;},
    '|': function() {turtle.right(PI)},
};

// Thanks to wikipedia for teaching me about l-systems:
// https://en.wikipedia.org/wiki/L-system
 
// Note: I learned from the L-system Wikipedia article that:
//       F means go forward
//       + means turn left by angle (which is 90 for Dragon curve)
//       - means turn left by same angle
//       X and Y mean \"do nothing\"
//           Their purpose is only to control the creation of the string

// Produce the string
// This means \"read the string\" and:
//    Replace X by something
//    Replace Y by something else
//    [...]
// We start with an initial string. (Example: FX)
function produce(initial){
    let string = initial;
    
	for(let j = 0; j < string.length; j++){
        let before = string.substr(0,j);
        let after = string.substr(j+1,string.length);
        
        for(let key in RULES){
        	if(string[j] == key){
                string = before + RULES[key] + after;
            	j += RULES[key].length-1;
            }
        }
    }
    
    return string;
}

function setup() {
    // For the dragon curve, we start with:
    string = START;
    execute_step = 0;
    // Generate the string only one time
    for(let i = 0; i < ITERATIONS; i++){
    	string = produce(string);
    }
}

// These are the actions we will take when executing the string


// Used to keep track of current position in string
var execute_step = 0;

// Run some characters of the string
function execute(string, steps_per_frame){
    for(let i = 0; i < steps_per_frame && execute_step < string.length; i++){
        // Reset turtle angle, background, etc at frame 0
        try {
            // Execute the function at given letter
            alphabet[string[execute_step]]();
        } catch (e) {
        	throw new Error("Error executing rule for: " + string[execute_step] + "\n" + e);
        }
        execute_step++;
        
	}
}

Canvas.setpenopacity(1);

setup();

// The walk function will be called until it returns false.
function walk(i) {
    let steps_per_frame = 1;
    
    execute(string, steps_per_frame);
    
    return i < 400000;
}