Yet another L-System ⛪

This Turtle allows up to 5 rules (X, Y, Z, F, G). Per such character you can set the rule for it while processing and the meaning of it in the resulting set after processing (while drawing). By default X, Y and Z are for rule evaluation only while F and G also draw when they end up in the evaluated result.

+ and - tell the turtle to turn right and left by angle.

The brackets [ and ] store the state and retrieve that state respectively, which is effectively branching.

So valid characters in rules are: X, Y, Z, F, G, [, ], + and -. More info on variables on mouseover.

en.wikipedia.org/wiki/l-system

Default for this turtle is the Hilbert Curve, gallery image is Dekking's Church: Yet another L-System ⛪ (variation)

Show me your results in the comments!

Log in to post a comment.

/*
Yet another L-System - By Jurgen Westerhof 2024
https://turtletoy.net/turtle/2db175d4c4

This Turtle allows up to 5 rules (X, Y, Z, F, G). Per such character you can set the rule for it while processing and the meaning of it in the resulting set after processing (while drawing). By default X, Y and Z are for rule evaluation only while F and G also draw when they end up in the evaluated result.

+ and - tell the turtle to turn right and left by angle.

The brackets [ and ] store the state and retrieve that state respectively, which is effectively branching.

So valid characters in rules are: X, Y, Z, F, G, [, ], + and -. More info on variables on mouseover.

https://en.wikipedia.org/wiki/L-system

Default for this turtle is the Hilbert Curve, gallery image is Dekking's Church: https://turtletoy.net/turtle/2db175d4c4#n=11,axiom=@R1pZWA,X=@LS1GKytZK0YtWi0tRisrWStGLVo,Y=@LS1GKytZLUYrWA,Z=@RkctRitY,F=@,G=@RkctRitYRkcrRi1a,onG=0

Show me your results in the comments!

Factal Binary Tree:		https://turtletoy.net/turtle/2db175d4c4#n=9,axiom=@Rg,F=@R1srRl0tRg,G=@R0c,angle=45,rotation=225
Von Koch:
- Curve: 				https://turtletoy.net/turtle/2db175d4c4#n=9,axiom=@Rg,F=@RitG4oiS4oiSRitG,angle=60,rotation=135
- (4, 1/3) curve:		https://turtletoy.net/turtle/2db175d4c4#axiom=@Rg,F=@Ri1GK0YrRi1G
- Flake: 				https://turtletoy.net/turtle/2db175d4c4#axiom=@RisrRisrRg,F=@Ri1GKytGLUY,angle=60
Sierpiński:
- Triangle: 			https://turtletoy.net/turtle/2db175d4c4#n=6,axiom=@Ri1HLUc,F=@Ri1HK0YrRy1G,G=@R0c,angle=120
- Triangle 2: 			https://turtletoy.net/turtle/2db175d4c4#n=7,axiom=@Rg,F=@Ry1GLUc,G=@RitHK0Y,angle=60
- Carpet: 				https://turtletoy.net/turtle/2db175d4c4#axiom=@Rg,F=@RitGLUYtRi1HK0YrRitGLUY,G=@R0dH,onG=1,rotation=45
- Median Curve: 		https://turtletoy.net/turtle/2db175d4c4#n=12,axiom=@WC0tRi0tWC0tRg,X=@K1ktRi1ZKw,Y=@LVgrRitYLQ,angle=45,rotation=45
- Square: 				https://turtletoy.net/turtle/2db175d4c4#axiom=@RitYRitGK1hG,X=@WEYtRitGLVhGK0YrWEYtRitGLVg,rotation=45
- Pentagon: 			https://turtletoy.net/turtle/2db175d4c4#axiom=@RitGK0YrRitG,F=@RitGWytGXS0tRitG,angle=72
Pentaflake:				https://turtletoy.net/turtle/2db175d4c4#axiom=@RisrRisrRisrRisrRg,F=@RisrRisrRisrKysrRi1GKytG,angle=36
Dragon Curve: 			https://turtletoy.net/turtle/2db175d4c4#n=16,axiom=@Rg,F=@RitH,G=@Ri1H
Fractal Plant: 			https://turtletoy.net/turtle/2db175d4c4#n=7,X=@RitbW1hdLVhdLUZbLUZYXStY,F=@RkY,angle=25,rotation=245
Hilbert Curve: 			https://turtletoy.net/turtle/2db175d4c4#n=6
Gosper:					https://turtletoy.net/turtle/2db175d4c4#n=4,X=@WC1ZLS1ZK1grK1hYK1kt,Y=@K1gtWVktLVktWCsrWCtZ,angle=60,onX=2,onY=2
Lévy C Curve: 			https://turtletoy.net/turtle/2db175d4c4#n=15,axiom=@Rg,F=@K0YtLUYr,angle=45
Snowflake: 				https://turtletoy.net/turtle/2db175d4c4#n=4,axiom=@Ri1GLUYtRi1G,F=@Ri1GKytGK0YtRi1G,angle=72
Peano:
- Curve 1:				https://turtletoy.net/turtle/2db175d4c4#n=4,X=@WEZZRlgrRitZRlhGWeKIkkbiiJJYRllGWA,Y=@WUZYRlniiJJG4oiSWEZZRlgrRitZRlhGWQ
- Curve 2:				https://turtletoy.net/turtle/2db175d4c4#n=4,axiom=@Rg,F=@RitGLUYtRkYtRi1GLUZG,rotation=45
Davis-Knuth Terdragon:	https://turtletoy.net/turtle/2db175d4c4#n=8,axiom=@Rg,F=@K0YtLS0tRisrKytGLQ,angle=30
- alternative:			https://turtletoy.net/turtle/2db175d4c4#n=8,axiom=@Rg,F=@RuKIkkYrRg,angle=120
Dekking:
- Quadratic Gosper:		https://turtletoy.net/turtle/2db175d4c4#n=3,axiom=@LVlG,X=@WEZYLVlGLVlGK0ZYK0ZYLVlGLVlGRlgrWUYrRlhGWFlGLUZYK1lGK0ZYRlgrWUYtRlhZRi1ZRi1GWCtGWCtZRllGLQ,Y=@K0ZYRlgtWUYtWUYrRlgrRlhZRitGWC1ZRllGLUZYLVlGK0ZYWUZZRi1GWC1ZRkZYK0ZYK1lGLVlGLUZYK0ZYK1lGWQ
- Church:				https://turtletoy.net/turtle/2db175d4c4#n=11,axiom=@R1pZWA,X=@LS1GKytZK0YtWi0tRisrWStGLVo,Y=@LS1GKytZLUYrWA,Z=@RkctRitY,F=@,G=@RkctRitYRkcrRi1a,onG=0

*/
const n = 5; //min=0 max=20 step=1 The number of rule application iterations
const axiom = 'X'; //type=string The axiom of the L-system
const X = '-YF+XFX+FY-'; //type=string Evolution rule for X, + is right, - is left, [ and ] can be used for branching
const Y = '+XF-YFY-FX+'; //type=string Evolution rule for Y, + is right, - is left, [ and ] can be used for branching
const Z = 'Z'; //type=string Evolution rule for Z, + is right, - is left, [ and ] can be used for branching
const F = 'F'; //type=string Evolution rule for F, + is right, - is left, [ and ] can be used for branching
const G = 'G'; //type=string Evolution rule for G, + is right, - is left, [ and ] can be used for branching

const angle = 90; //min=0 max=360 step=1 The angle to turn left (- character) or right (+ character)
const onX = 0; //min=0 max=2 step=1 (Apply rules only (no moving or drawing), Apply rules and move forward (no drawing), Apply rules and draw while moving forward)
const onY = 0; //min=0 max=2 step=1 (Apply rules only (no moving or drawing), Apply rules and move forward (no drawing), Apply rules and draw while moving forward)
const onZ = 0; //min=0 max=2 step=1 (Apply rules only (no moving or drawing), Apply rules and move forward (no drawing), Apply rules and draw while moving forward)
const onF = 2; //min=0 max=2 step=1 (Apply rules only (no moving or drawing), Apply rules and move forward (no drawing), Apply rules and draw while moving forward)
const onG = 2; //min=0 max=2 step=1 (Apply rules only (no moving or drawing), Apply rules and move forward (no drawing), Apply rules and draw while moving forward)
const rotation = 0; //min=0 max=360 step=1 Rotate the result

Canvas.setpenopacity(1);
loadLSystem();

const rules = {
    X: [X, onX],
    Y: [Y, onY],
    Z: [Z, onZ],
    F: [F, onF],
    G: [G, onG],
}

const ls = new LSystem(axiom, rules, angle).process(n);

const turtle = initializeCenteredScaledTurtleForLSystem(ls, rotation);

function walk(i) {
    ls.drawNext(turtle);
    return ls.hasNext();
}

function loadLSystem() {
    this.LSystemActions = {RulesOnly: 0, RulesMove: 1, RulesMoveDraw: 2}

    class LSystem {
        // processing
        #rules = {};
        #wip = [];
        //instructions result
        #result = '';
        //drawing
        #angle = 90;
        #distance = 1;
        #cmdPointer = 0;
        #states = [];
        //methods
        constructor(axiom, rules, angle) {
            this.#rules  =  Object.assign(this.#rules, rules);

            this.#wip    =  axiom.split('');
            this.#result =  axiom;

            this.#angle =  angle;
        }
        process(iterations = 1) {
            for (let i = 0; i < iterations; i++) {
                this.#wip = this.#wip.flatMap(e => (e in this.#rules)? this.#rules[e][0].split(''): [e]);
            }
            this.#result = this.#wip.join('');
            return this;
        }
        scale(scale) {
            this.#distance = scale;
        }
        next() {
            return this.#wip[this.#cmdPointer++];
        }
        hasNext() {
            return this.#cmdPointer < this.#wip.length;
        }
        drawNext(turtle, dryrun = false) {
            const cmd = this.next();
            switch (cmd) {
                case "+":   return turtle.right(this.#angle);
                case "−":
                case "-":   return turtle.left(this.#angle);
                case "[":   return this.#states.push([turtle.pos(), turtle.h()]);
                case "]":   return [this.#states.pop()].forEach(s => {turtle.jump(s[0]); turtle.seth(s[1]);});
                case 'X':
                case 'Y':
                case 'Z':
                case 'F':
                case 'G':   switch(this.#rules[cmd][1]) {
                                case LSystemActions.RulesMove:     turtle.up();
                                case LSystemActions.RulesMoveDraw: turtle.forward(this.#distance);
                                                            if(!dryrun) turtle.down();
                                case LSystemActions.RulesOnly:
                                default:
                            }
                default:    return;
            }
        }
        resetDraw() {
            this.#cmdPointer = 0;
        }
    }
    this.LSystem = LSystem;
    
    function initializeCenteredScaledTurtleForLSystem(ls, rotation) {
        const turtle = new MeasuringTurtle();
        turtle.up();
        turtle.seth(rotation);
    
        while(ls.hasNext()) {
            ls.drawNext(turtle, true);
        }
        ls.resetDraw();
    
        const box = turtle.getBox();
        const distance = 190 / Math.max(box.width, box.height);
    
        ls.scale(distance)
    
        const t = new Turtle(-box.center[0] * distance, -box.center[1] * distance);
        t.seth(rotation);
        t.down();
        
        return t;
    }
    this.initializeCenteredScaledTurtleForLSystem = initializeCenteredScaledTurtleForLSystem;
}

function MeasuringTurtle(x, y) {
    class MeasuringTurtle extends Turtle {
        box = [[0,0], [0,0]];
        constructor(x, y) {
            super(x, y);
        }
        goto(x, y) {
            super.goto(x, y);
            if(this.box === undefined) this.resetBox();
            const p = this.pos();
            this.box[0][0] = Math.min(this.box[0][0], p[0]);
            this.box[0][1] = Math.min(this.box[0][1], p[1]);
            this.box[1][0] = Math.max(this.box[1][0], p[0]);
            this.box[1][1] = Math.max(this.box[1][1], p[1]);
        }
        resetBox() {
            this.box = [this.pos(),this.pos()];
        }
        getBox() {
            return {
                0: [...this.box[0]],
                1: [...this.box[1]],
                width: this.box[1][0] - this.box[0][0],
                height: this.box[1][1] - this.box[0][1],
                topLeft: [...this.box[0]],
                bottomRight: [...this.box[1]],
                center: [this.box[0][0]+(this.box[1][0] - this.box[0][0])/2, this.box[0][1]+(this.box[1][1] - this.box[0][1])/2]
            };
        }
    }
    return new MeasuringTurtle(x, y);
}