xxxxxxxxxx
241
// Surrond items that we want to lerp with () in the rules
// () characters are then stripped from the next generation
const rules = {
X: [
// Original rule
{ rule: "(F[RX][RX]FX)", prob: 0.5 },
// Fewer limbs
{ rule: "(F[RX]FX)", prob: 0.05 },
{ rule: "(F[RX]FX)", prob: 0.05 },
// Extra rotation
{ rule: "(F[RRX][RX]FX)", prob: 0.1 },
{ rule: "(F[RX][RRX]FX)", prob: 0.1 },
// Berries/fruits
{ rule: "(F[RX][RX]FXA)", prob: 0.1 },
{ rule: "(F[RX][RX]FXB)", prob: 0.1 }
],
F: [
// Original rule
{ rule: "F(F)", prob: 0.8 },
// Extra growth
{ rule: "F(FF)", prob: 0.05 },
// Stunted growth
{ rule: "F", prob: 0.05 },
// Rotated growth
{ rule: "RFR", prob: 0.05 },
// New limbs
{ rule: "F([RX])", prob: 0.025 },
{ rule: "F([RX])", prob: 0.025 },
],
"(": "",
")": "",
"R": [
// R becomes some random rotation
// +/- for x </> for z
{ rule: "+", prob: 0.25 },
{ rule: "-", prob: 0.25},
{ rule: "<", prob: 0.25},
{ rule: ">", prob: 0.25},
]
};
const len = 4;
const ang = 25;
let drawRules;
let word = "X";
const maxGeneration = 6;
let currGeneration = 0;
let growthPercent = 1;
const growthRate = 0.04;
const showGrowth = true;
function setup() {
createCanvas(600, 600, WEBGL);
noStroke();
if(!showGrowth) {
fullyGrow();
}
drawRules = {
"A": (t) => {
// Draw circle at current location
fill("#E5CEDC");
sphere(len * 2 * t);
},
"B": (t) => {
// Draw circle at current location
fill("#FCA17D");
sphere(len * 2 * t);
},
"F": (t) => {
// Draw line forward, then move to end of line
fill("#9ea93f");
// cylinder is drawn from mid point, so move halfway along the line
translate(0, -len/2 * t);
cylinder(len/2, len * t);
// Don't forget to translate the rest of the way!
translate(0, -len/2 * t);
},
"+": (t) => {
// Rotate forward
rotateZ(PI/180 * -ang * t);
rotateY(PI/180 * -ang/10 * t);
},
"-": (t) => {
// Rotate back
rotateY(PI/180 * ang/10 * t);
rotateZ(PI/180 * ang * t);
},
"<": (t) => {
// Rotate forward
rotateX(PI/180 * -ang * t);
rotateY(PI/180 * -ang/10 * t);
},
">": (t) => {
// Rotate back
rotateY(PI/180 * ang/10 * t);
rotateX(PI/180 * ang * t);
},
// Save current location
"[": push,
// Restore last location
"]": pop,
};
}
function draw() {
background(0);
if(showGrowth) {
if(growthPercent < 1) {
const mod = (currGeneration + growthPercent);
growthPercent += growthRate/mod;
} else {
nextGeneration();
}
}
translate(0, height/2, -width/2);
rotateY(mouseX/width * TAU);
// rotateX(millis()/1000);
drawLsysLerp(word, growthPercent);
}
function mouseReleased() {
if(showGrowth) {
nextGeneration();
} else {
fullyGrow();
}
}
function nextGeneration() {
if(growthPercent < 1) {
return;
}
if(currGeneration === maxGeneration) {
currGeneration = 0;
word = "X";
}
word = generate(word);
currGeneration ++;
growthPercent = 0;
}
function fullyGrow() {
word = "X";
for(let i = 0; i < maxGeneration; i ++) {
word = generate(word);
}
}
function generate(word) {
let next = ""
for(let i = 0; i < word.length; i ++) {
let c = word[i];
if(c in rules) {
let rule = rules[c];
// Check if we're using an array or not
if(Array.isArray(rule)) {
next += chooseOne(rule); // If we are, choose one of the options
} else {
next += rules[c]; // Otherwise use the rule directly
}
} else {
next += c;
}
}
return next;
}
function chooseOne(ruleSet) {
let n = random(); // Random number between 0-1
let t = 0;
for(let i = 0; i < ruleSet.length; i++) {
t += ruleSet[i].prob; // Keep adding the probability of the options to total
if(t > n) { // If the total is more than the random value
return ruleSet[i].rule; // Choose that option
}
}
return "";
}
function drawLsysLerp(state, t) {
t = constrain(t, 0, 1);
let lerpOn = false;
push();
for(let i = 0; i < state.length; i ++) {
let c = state[i];
if(c === "(") {
lerpOn = true;
continue;
}
if(c === ")") {
lerpOn = false;
continue;
}
let lerpT = t;
if(!lerpOn) {
lerpT = 1;
}
if(c in drawRules) {
drawRules[c](lerpT);
}
}
pop();
}