Added randomisation to allow variation useful in organic growth procedures.
The instruction set is extended and includes:
& to invert interpretation of + and -
< and > to respectively decrease and increase distance travelled going forward by scaleFactor
/ to modify the distance travelled going forward by a random value between scaleRndMin and scaleRndMax
\ to reset the distance travelled going forward
! to modify the turning angle by a random value between angleRndMin and angleRndMax
~ to reset that angle
Combined valid characters in rules are: X, Y, Z, F, G, [, ], +, -, &, <, >, /, \, !, ~. More info on variables on mouseover.
The Turtle this is forked from 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.
#lsystem #noise
Log in to post a comment.
// Forked from "Yet another L-System ⛪" by Jurgen
// https://turtletoy.net/turtle/2db175d4c4
/*
rndL-System - By Jurgen Westerhof 2025
https://turtletoy.net/turtle/3f34b6d982
Mimic Random weed - https://turtletoy.net/turtle/c31e5055d1
https://turtletoy.net/turtle/3f34b6d982#rndSeed=@YXNmZ2RkZ2hkZw,n=5,axiom=@WA,X=@RiEtW1tYWF0rWF0rRlsrRlhYXSEtWA,Y=@,Z=@,F=@RkY,G=@,angle=22.5,angleRndMin=0,angleRndMax=1,scaleFactor=1.36,scaleRndMin=0.5,scaleRndMax=1.5,onX=0,onY=0,onZ=0,onF=2,onG=2,rotation=270
Generating trees with randomization:
https://turtletoy.net/turtle/3f34b6d982#rndSeed=@,n=5,axiom=@Rg,X=@,Y=@,Z=@,F=@R1svISsrKysrKytGflxdJlsvIeKIkuKIkuKIkuKIkuKIkkZ+XF0gK0dbLyErKysrKytGflxdWy8h4oiS4oiS4oiS4oiSRn5cXSArIEdbLyErKysrK0Z+XF1bLyHiiJLiiJLiiJLiiJLiiJLiiJLiiJJGflxdICtG,G=@RyZH,angle=7,angleRndMin=0.59,angleRndMax=1.47,scaleFactor=1.04,scaleRndMin=0.66,scaleRndMax=1.31,onX=0,onY=0,onZ=0,onF=2,onG=2,rotation=270
Pernrose
https://turtletoy.net/turtle/3f34b6d982#n=5,axiom=@W1hdKytbWF0rK1tYXSsrW1hdKytbWF0,X=@K1lGLS1aRlstLS1HRi0tWEZdKw,Y=@LUdGKytYRlsrKytZRisrWkZdLQ,Z=@LS1ZRisrKytHRlsrWkYrKysrWEZdLS1YRg,F=@,G=@WUYrK1pGLS0tLVhGWy1ZRi0tLS1HRl0rKw,angle=36,onX=0,onY=0,onZ=0,onF=2,onG=0,rotation=45
Space filling, dragon variation:
https://turtletoy.net/turtle/3f34b6d982#n=5,axiom=@WA,X=@WCtGK1grRitYLUYtWC1GLVg,Y=@,Z=@,F=@Rg,G=@,angle=45,onX=0,onY=0,onZ=0,onF=2,onG=2,rotation=0
Rose:
https://turtletoy.net/turtle/3f34b6d982#rndSeed=@YXNmZ2RkZ2hkZ2hnaWprbW5oZ2l1eWtqaGlpdWh5Z3Zua2poamlnaA,n=7,axiom=@Rg,X=@,Y=@,Z=@,F=@RisrRg,G=@,angle=77,angleRndMin=0,angleRndMax=1,scaleFactor=1.36,scaleRndMin=0.5,scaleRndMax=1.5,onX=0,onY=0,onZ=0,onF=2,onG=2,rotation=122
Added randomisation to allow variation useful in organic growth procedures.
The instruction set is extended and includes:
& to invert interpretation of + and -
< and > to respectively decrease and increase distance travelled going forward by scaleFactor
/ to modify the distance travelled going forward by a random value between scaleRndMin and scaleRndMax
\ to reset the distance travelled going forward
! to modify the turning angle by a random value between angleRndMin and angleRndMax
~ to reset that angle
Combined valid characters in rules are: X, Y, Z, F, G, [, ], +, -, &, <, >, /, \, !, ~. More info on variables on mouseover.
The Turtle this is forked from 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.
*/
const rndSeed = ''; //type=string Change me, empty is random each run
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 = 'F!-[[XX]+X]+F[+FXX]!-X'; //type=string Evolution rule for X, + is right, - is left, [ and ] can be used for branching
const Y = ''; //type=string Evolution rule for Y, + is right, - is left, [ and ] can be used for branching
const Z = ''; //type=string Evolution rule for Z, + is right, - is left, [ and ] can be used for branching
const F = 'FF'; //type=string Evolution rule for F, + is right, - is left, [ and ] can be used for branching
const G = ''; //type=string Evolution rule for G, + is right, - is left, [ and ] can be used for branching
const angle = 22.5; //min=0 max=360 step=.1 The angle to turn left (- character) or right (+ character)
const angleRndMin = 0; //min=-5 max=5 step=.01 Used with ! and ~ operators
const angleRndMax = 1; //min=-5 max=5 step=.01 Used with ! and ~ operators
const scaleFactor = 1.36; //min=1 max=5 step=.01 The scale factor to be used with > and < commands
const scaleRndMin = .5; //min=-5 max=5 step=.01 Used with / and \ operators
const scaleRndMax = 1.5; //min=-5 max=5 step=.01 Used with / and \ operators
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 = 270; //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],
}
turtlelib_init();
const useSeed = rndSeed == ''? (new Date()).getMilliseconds() + ('' + new Date()).substring(0, 24): rndSeed;
R.seed(useSeed);
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
#baseAngle = 90;
#angle = 90;
#baseDistance = 1;
#distance = 1;
#cmdPointer = 0;
#states = [];
#plusMinusAngleInterpretation = 1;
//methods
constructor(axiom, rules, angle) {
this.#rules = Object.assign(this.#rules, rules);
this.#wip = axiom.split('');
this.#result = axiom;
this.#baseAngle = angle;
this.#angle = this.#baseAngle;
}
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.#baseDistance = scale;
this.#distance = this.#baseDistance;
}
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 * this.#plusMinusAngleInterpretation);
case "−":
case "-": return turtle.left(this.#angle * this.#plusMinusAngleInterpretation);
case "&": return this.#plusMinusAngleInterpretation = ((this.#plusMinusAngleInterpretation + 4) % 4) - 2;
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 ">": return this.#distance *= scaleFactor;
case "<": return this.#distance /= scaleFactor;
case "!": return this.#angle = this.#baseAngle * (angleRndMin + (angleRndMax - angleRndMin) * Math.random());
case "~": return this.#angle = this.#baseAngle;
case '/': return this.#distance = this.#baseDistance * (scaleRndMin + (scaleRndMax - scaleRndMin) * Math.random());
case "\\": return this.#distance = this.#baseDistance;
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.#distance = this.#baseDistance;
this.#angle = this.#baseAngle;
this.#plusMinusAngleInterpretation = 1;
this.#states = [];
}
}
this.LSystem = LSystem;
function initializeCenteredScaledTurtleForLSystem(ls, rotation) {
const measure = new MeasuringTurtle();
measure.up();
measure.seth(rotation);
R.seed(useSeed);
while (ls.hasNext()) {
ls.drawNext(measure, true);
}
ls.resetDraw();
const box = measure.getBox();
const scale = 190 / Math.max(box.width, box.height);
ls.scale(scale);
R.seed(useSeed);
const offsetX = -box.center[0] * scale;
const offsetY = -box.center[1] * scale;
const t = new Slowpoke(offsetX, offsetY);
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) {
if(this.box === undefined) this.resetBox();
super.goto(x, y);
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[1][0] + this.box[0][0])/2, (this.box[1][1] + this.box[0][1])/2]
};
}
}
return new MeasuringTurtle(x, y);
}
////////////////////////////////////////////////////////////////
// Slowpoke utility code. Created by Reinder Nijhoff 2019
// https://turtletoy.net/turtle/cfe9091ad8
// Fix 2025: the toString(8) check fails for 0.00000000 and -0.00000000. Fixed by adding 500
////////////////////////////////////////////////////////////////
function Slowpoke(x, y) {
const linesDrawn = {};
class Slowpoke extends Turtle {
goto(x, y) {
const p = Array.isArray(x) ? [...x] : [x, y];
if (this.isdown()) {
const [h1, h2] = ((p, o) => [
o[0].toFixed(8)+'_'+p[0].toFixed(8)+o[1].toFixed(8)+'_'+p[1].toFixed(8),
p[0].toFixed(8)+'_'+o[0].toFixed(8)+p[1].toFixed(8)+'_'+o[1].toFixed(8)
])(p.map(e => e+500), [this.x()+500, this.y()+500]);
if (linesDrawn[h1] || linesDrawn[h2]) {
super.up();
super.goto(p);
super.down();
return;
}
linesDrawn[h1] = linesDrawn[h2] = true;
}
super.goto(p);
}
}
return new Slowpoke(x,y);
}
// Below is automatically maintained by Turtlelib 1.0
// Changes below this comment might interfere with its correct functioning.
function turtlelib_init() {
turtlelib_ns_13b81fd40e_Jurgen_Randomness();
}
// Turtlelib Jurgen Randomness v 2 - start - {"id":"13b81fd40e","package":"Jurgen","name":"Randomness","version":"2"}
function turtlelib_ns_13b81fd40e_Jurgen_Randomness() {
///////////////////////////////////////////////////////////////
// Pseudorandom functions - Created by Jurgen Westerhof 2024 //
///////////////////////////////////////////////////////////////
class Random {
static #apply(seed) {
// Seedable random number generator by David Bau: http://davidbau.com/archives/2010/01/30/random_seeds_coded_hints_and_quintillions.html
!function(a,b,c,d,e,f,g,h,i){function j(a){var b,c=a.length,e=this,f=0,g=e.i=e.j=0,h=e.S=[];for(c||(a=[c++]);d>f;)h[f]=f++;for(f=0;d>f;f++)h[f]=h[g=s&g+a[f%c]+(b=h[f])],h[g]=b;(e.g=function(a){for(var b,c=0,f=e.i,g=e.j,h=e.S;a--;)b=h[f=s&f+1],c=c*d+h[s&(h[f]=h[g=s&g+b])+(h[g]=b)];return e.i=f,e.j=g,c})(d)}function k(a,b){var c,d=[],e=typeof a;if(b&&"object"==e)for(c in a)try{d.push(k(a[c],b-1))}catch(f){}return d.length?d:"string"==e?a:a+"\0"}function l(a,b){for(var c,d=a+"",e=0;e<d.length;)b[s&e]=s&(c^=19*b[s&e])+d.charCodeAt(e++);return n(b)}function m(c){try{return o?n(o.randomBytes(d)):(a.crypto.getRandomValues(c=new Uint8Array(d)),n(c))}catch(e){return[+new Date,a,(c=a.navigator)&&c.plugins,a.screen,n(b)]}}function n(a){return String.fromCharCode.apply(0,a)}var o,p=c.pow(d,e),q=c.pow(2,f),r=2*q,s=d-1,t=c["seed"+i]=function(a,f,g){var h=[];f=1==f?{entropy:!0}:f||{};var o=l(k(f.entropy?[a,n(b)]:null==a?m():a,3),h),s=new j(h);return l(n(s.S),b),(f.pass||g||function(a,b,d){return d?(c[i]=a,b):a})(function(){for(var a=s.g(e),b=p,c=0;q>a;)a=(a+c)*d,b*=d,c=s.g(1);for(;a>=r;)a/=2,b/=2,c>>>=1;return(a+c)/b},o,"global"in f?f.global:this==c)};if(l(c[i](),b),g&&g.exports){g.exports=t;try{o=require("crypto")}catch(u){}}else h&&h.amd&&h(function(){return t})}(this,[],Math,256,6,52,"object"==typeof module&&module,"function"==typeof define&&define,"random");
Math.seedrandom(seed);
}
static seedRandom() { this.#apply(new Date().getMilliseconds()); }
static seedDaily() { this.#apply(new Date().toDateString()); }
static seed(seed) { this.#apply(seed); }
static getInt(min, max) { if(max == undefined) {max = min + 1; min = 0; } const [mi, ma] = [Math.min(min, max), Math.max(min, max)]; return (mi + Math.random() * (ma - mi)) | 0;}
static get(min, max) {if(min == undefined) {return Math.random();}if(max == undefined) {max = min;min = 0;}const [mi, ma] = [Math.min(min, max), Math.max(min, max)];return mi + Math.random() * (ma - mi);}
static fraction(whole) { return this.get(0, whole); }
static getAngle(l = 1) { return l * this.get(0, 2*Math.PI); }
// Standard Normal variate using Box-Muller transform.
static getGaussian(mean=.5, stdev=.1) {const u = 1 - this.get(); /* Converting [0,1) to (0,1] */const v = this.get();const z = ( -2.0 * Math.log( u ) )**.5 * Math.cos( 2.0 * Math.PI * v );/* Transform to the desired mean and standard deviation: */return z * stdev + mean;}
static skew(value, skew = 0) { /*skew values (from 0 to 1) by a skew from -1 to 1, respectively right and left skewed (resp more values to left or to right), 0 is not skewed*/return (skew < 0)? value - (this.skew(1-value, -skew) - (1-value)): Math.pow(value, 1-Math.abs(skew));}
static getNormalDistributed(skew = 0) { /*skew values (from 0 to 1) by a skew from -1 to 1, respectively right and left skewed (resp more values to left or to right), 0 is not skewed*/let v = -1;while(v < 0 || 1 <= v) { v = this.getGaussian(.5, .1) };return this.skew(v, skew);}
}
this.R = Random;
}
// Turtlelib Jurgen Randomness v 2 - end