added AI grebed presets and ugly collision check function

This commit is contained in:
Jürg Hallenbarter
2026-04-27 18:59:48 +00:00
parent f44a6e6892
commit 9ba379ed29

View File

@@ -1,6 +1,8 @@
/* /*
L-System Fractal Generator L-System Fractal Generator
https://paulbourke.net/fractals/lsys/
The following characters have a geometric interpretation: The following characters have a geometric interpretation:
Character Meaning Character Meaning
@@ -40,25 +42,51 @@ let axiom = 'F+F+F+F';
let maxIterations = 4; let maxIterations = 4;
const maxCalculations = 50000; const maxCalculations = 50000;
let rules = { let rules = {
'F': 'FF+F-F+F+FF' 'F': 'FF+F++F+F'
}; };
// Generate the fractal string from axiom and rules //* L-System presets array **AI generated**
for (let i = 0; i < maxIterations; i++) { const presets = [
if (axiom.length > maxCalculations) {//hard coded limit to prevent infinite loops { name: 'Crystal', axiom: 'F+F+F+F', rules: { 'F': 'FF+F++F+F' }, angle: 90 },
console.log('Reached max calculations, stopping at iteration', i); { name: 'Koch Curve', axiom: 'F+F+F+F', rules: { 'F': 'F+F-F-FF+F+F-F' }, angle: 90 },
break; { name: 'Von Koch Snowflake', axiom: 'F++F++F', rules: { 'F': 'F-F++F-F' }, angle: 60 },
} { name: 'Hilbert', axiom: 'X', rules: { 'X': '-YF+XFX+FY-', 'Y': '+XF-YFY-FX+' }, angle: 90 },
let newAxiom = ''; { name: 'Dragon Curve', axiom: 'FX', rules: { 'X': 'X+YF+', 'Y': '-FX-Y' }, angle: 90 },
for (let char of axiom) {//loop through each character in the axiom and replace it with the corresponding rule { name: 'Levy Curve', axiom: 'F', rules: { 'F': '-F++F-' }, angle: 45 },
if (rules[char]) { { name: 'Sierpinski Arrowhead', axiom: 'YF', rules: { 'X': 'YF+XF+Y', 'Y': 'XF-YF-X' }, angle: 60 },
newAxiom += rules[char]; { name: 'Peano Curve', axiom: 'X', rules: { 'X': 'XFYFX+F+YFXFY-F-XFYFX', 'Y': 'YFXFY-F-XFYFX+F+YFXFY' }, angle: 90 },
} else { { name: 'Plant', axiom: 'X', rules: { 'X': 'F+[[X]-X]-F[-FX]+X', 'F': 'FF' }, angle: 25 },
newAxiom += char; { name: 'Triangle', axiom: 'F+F+F', rules: { 'F': 'F-F+F' }, angle: 120 },
} { name: 'Quadratic Gosper', axiom: '-YF', rules: { 'X': 'XFX-YF-YF+FX+FX-YF-YFFX+YF+FXFXYF-FX+YF+FXFX+YF-FXYF-YF-FX+FX+YFYF-', 'Y': '+FXFX-YF-YF+FX+FXYF+FX-YFYF-FX-YF+FXYFYF-FX-YFFX+FX+YF-YF-FX+FX+YFY' }, angle: 90 },
} { name: 'Square Sierpinski', axiom: 'F+XF+F+XF', rules: { 'X': 'XF-F+F-XF+F+XF-F+F-X' }, angle: 90 },
axiom = newAxiom; { name: 'Quadratic Snowflake', axiom: 'F', rules: { 'F': 'F-F+F+F-F' }, angle: 90 },
} { name: 'Quadratic Koch Island', axiom: 'F+F+F+F', rules: { 'F': 'F+F-F-FFF+F+F-F' }, angle: 90 },
{ name: 'Board', axiom: 'F+F+F+F', rules: { 'F': 'FF+F+F+F+FF' }, angle: 90 },
{ name: 'Sierpinski Arrowhead 2', axiom: 'YF', rules: { 'X': 'YF+XF+Y', 'Y': 'XF-YF-X' }, angle: 60 },
{ name: 'Cross 1', axiom: 'F+F+F+F', rules: { 'F': 'F+FF++F+F' }, angle: 90 },
{ name: 'Cross 2', axiom: 'F+F+F+F', rules: { 'F': 'F+F-F+F+F' }, angle: 90 },
{ name: 'Pentaplexity', axiom: 'F++F++F++F++F', rules: { 'F': 'F++F++F|F-F++F' }, angle: 36 },
{ name: 'Tiles', axiom: 'F+F+F+F', rules: { 'F': 'FF+F-F+F+FF' }, angle: 90 },
{ name: 'Rings', axiom: 'F+F+F+F', rules: { 'F': 'FF+F+F+F+F+F-F' }, angle: 90 },
{ name: 'Hexagonal Gosper', axiom: 'XF', rules: { 'X': 'X+YF++YF-FX--FXFX-YF+', 'Y': '-FX+YFYF++YF+FX--FX-Y' }, angle: 60 },
{ name: 'Classic Sierpinski Curve', axiom: 'F--XF--F--XF', rules: { 'X': 'XF+F+XF--F--XF+F+X' }, angle: 45 },
{ name: 'Krishna Anklets', axiom: '-X--X', rules: { 'X': 'XFX--XFX' }, angle: 45 },
{ name: 'Mango Leaf', axiom: 'Y---Y', rules: { 'X': '{F-F}{F-F}--[--X]{F-F}{F-F}--{F-F}{F-F}--', 'Y': 'f-F+X+F-fY' }, angle: 60 },
{ name: 'Snake Kolam', axiom: 'F+XF+F+XF', rules: { 'X': 'X{F-F-F}+XF+F+X{F-F-F}+X' }, angle: 90 },
{ name: 'Leaf', axiom: 'a', rules: { 'F': '>F<', 'a': 'F[+x]Fb', 'b': 'F[-y]Fa', 'x': 'a', 'y': 'b' }, angle: 45 },
{ name: 'Bush 1', axiom: 'Y', rules: { 'X': 'X[-FFF][+FFF]FX', 'Y': 'YFX[+Y][-Y]' }, angle: 25.7 },
{ name: 'Bush 2', axiom: 'F', rules: { 'F': 'FF+[+F-F-F]-[-F+F+F]' }, angle: 22.5 },
{ name: 'Bush 3', axiom: 'F', rules: { 'F': 'F[+FF][-FF]F[-F][+F]F' }, angle: 35 },
{ name: 'Bush 4 (Saupe)', axiom: 'VZFFF', rules: { 'V': '[+++W][---W]YV', 'W': '+X[-W]Z', 'X': '-W[+X]Z', 'Y': 'YZ', 'Z': '[-FFF][+FFF]F' }, angle: 20 },
{ name: 'Bush 5', axiom: 'FX', rules: { 'X': '>[-FX]+FX' }, angle: 40 },
{ name: 'Sticks', axiom: 'X', rules: { 'F': 'FF', 'X': 'F[+X]F[-X]+X' }, angle: 20 },
{ name: 'Algae 1', axiom: 'aF', rules: { 'a': 'FFFFFv[+++h][---q]fb', 'b': 'FFFFFv[+++h][---q]fc', 'c': 'FFFFFv[+++fa]fd', 'd': 'FFFFFv[+++h][---q]fe', 'e': 'FFFFFv[+++h][---q]fg', 'g': 'FFFFFv[---fa]fa', 'h': 'ifFF', 'i': 'fFFF[--m]j', 'j': 'fFFF[--n]k', 'k': 'fFFF[--o]l', 'l': 'fFFF[--p]', 'm': 'fFn', 'n': 'fFo', 'o': 'fFp', 'p': 'fF', 'q': 'rfF', 'r': 'fFFF[++m]s', 's': 'fFFF[++n]t', 't': 'fFFF[++o]u', 'u': 'fFFF[++p]', 'v': 'Fv' }, angle: 12 },
{ name: 'Algae 2', axiom: 'aF', rules: { 'a': 'FFFFFy[++++n][----t]fb', 'b': '+FFFFFy[++++n][----t]fc', 'c': 'FFFFFy[++++n][----t]fd', 'd': '-FFFFFy[++++n][----t]fe', 'e': 'FFFFFy[++++n][----t]fg', 'g': 'FFFFFy[+++fa]fh', 'h': 'FFFFFy[++++n][----t]fi', 'i': '+FFFFFy[++++n][----t]fj', 'j': 'FFFFFy[++++n][----t]fk', 'k': '-FFFFFy[++++n][----t]fl', 'l': 'FFFFFy[++++n][----t]fm', 'm': 'FFFFFy[---fa]fa', 'n': 'ofFFF', 'o': 'fFFFp', 'p': 'fFFF[-s]q', 'q': 'fFFF[-s]r', 'r': 'fFFF[-s]', 's': 'fFfF', 't': 'ufFFF', 'u': 'fFFFv', 'v': 'fFFF[+s]w', 'w': 'fFFF[+s]x', 'x': 'fFFF[+s]', 'y': 'Fy' }, angle: 12 },
{ name: 'Weed', axiom: 'F', rules: { 'F': 'FF-[XY]+[XY]', 'X': '+FY', 'Y': '-FX' }, angle: 22.5 },
{ name: 'Kolam', axiom: 'D--D', rules: { 'A': 'F++FFFF--F--FFFF++F++FFFF--F', 'B': 'F--FFFF++F++FFFF--F--FFFF++F', 'C': 'BFA--BFA', 'D': 'CFC--CFC' }, angle: 45 }
];
let presetIndex = 16;
function setup() { function setup() {
createCanvas(windowWidth, windowHeight); createCanvas(windowWidth, windowHeight);
@@ -66,10 +94,158 @@ function setup() {
noFill(); noFill();
stroke(0, 0, 0); stroke(0, 0, 0);
strokeWeight(currentLineWidth); strokeWeight(currentLineWidth);
currentPosition = [width / 2, height - 100];
// Load the selected preset
if (presetIndex >= 0 && presetIndex < presets.length) {
const preset = presets[presetIndex];
axiom = preset.axiom;
rules = {};
for (let key in preset.rules) {
rules[key] = preset.rules[key];
}
turningAngle = preset.angle * Math.PI / 180;
console.log('Loaded preset:', preset.name);
} else {
console.log('Invalid preset index:', presetIndex, 'using default');
const preset = presets[0];
axiom = preset.axiom;
rules = {};
for (let key in preset.rules) {
rules[key] = preset.rules[key];
}
turningAngle = preset.angle * Math.PI / 180;
}
// Generate the fractal string
let tempAxiom = axiom;
for (let i = 0; i < maxIterations; i++) {
if (tempAxiom.length > maxCalculations) {
console.log('Reached max calculations, stopping at iteration', i);
break;
}
let newAxiom = '';
for (let char of tempAxiom) {
if (rules[char]) {
newAxiom += rules[char];
} else {
newAxiom += char;
}
}
tempAxiom = newAxiom;
}
axiom = tempAxiom;
// Calculate bounding box to fit fractal in frame
let bounds = calculateBounds(axiom);
let fractalWidth = bounds.maxX - bounds.minX;
let fractalHeight = bounds.maxY - bounds.minY;
// Calculate scale factor to fit in canvas with padding
let padding = 50;
let scaleX = (width - padding * 2) / fractalWidth;
let scaleY = (height - padding * 2) / fractalHeight;
let scaleFactor = Math.min(scaleX, scaleY);
// Calculate translation to center the fractal
let centerX = (bounds.minX + bounds.maxX) / 2;
let centerY = (bounds.minY + bounds.maxY) / 2;
let translateX = width / 2 - centerX * scaleFactor;
let translateY = height / 2 - centerY * scaleFactor;
// Apply transformation
translate(translateX, translateY);
scale(scaleFactor, scaleFactor);
// Reset position to origin
currentPosition = [0, 0];
generateFractal(); generateFractal();
} }
// * real ugly but working AI-generated function to calculate bounds
function calculateBounds(axiomString) {
let minX = 0, maxX = 0, minY = 0, maxY = 0;
let x = 0, y = 0;
let angle = Math.PI * 1.5;
let localTurningAngle = turningAngle;
let localLineLength = lineLength;
let localStateStack = [];
let localSwapPlusMinus = false;
let localLineLengthScaleFactor = lineLengthScaleFactor;
for (let i = 0; i < axiomString.length; i++) {
let c = axiomString.charAt(i);
switch (c) {
case "F":
x += Math.cos(angle) * localLineLength;
y += Math.sin(angle) * localLineLength;
minX = Math.min(minX, x);
maxX = Math.max(maxX, x);
minY = Math.min(minY, y);
maxY = Math.max(maxY, y);
break;
case "f":
x += Math.cos(angle) * localLineLength;
y += Math.sin(angle) * localLineLength;
minX = Math.min(minX, x);
maxX = Math.max(maxX, x);
minY = Math.min(minY, y);
maxY = Math.max(maxY, y);
break;
case "+":
if (localSwapPlusMinus) {
angle -= localTurningAngle;
} else {
angle += localTurningAngle;
}
break;
case "-":
if (localSwapPlusMinus) {
angle += localTurningAngle;
} else {
angle -= localTurningAngle;
}
break;
case "|":
angle += Math.PI;
break;
case "[":
localStateStack.push({
x: x,
y: y,
angle: angle,
lineLength: localLineLength,
swapPlusMinus: localSwapPlusMinus
});
break;
case "]":
if (localStateStack.length > 0) {
let state = localStateStack.pop();
x = state.x;
y = state.y;
angle = state.angle;
localLineLength = state.lineLength;
localSwapPlusMinus = state.swapPlusMinus;
}
break;
case ">":
localLineLength *= localLineLengthScaleFactor;
break;
case "<":
localLineLength /= localLineLengthScaleFactor;
break;
case "&":
localSwapPlusMinus = !localSwapPlusMinus;
break;
}
}
return { minX, maxX, minY, maxY };
}
function generateFractal() { function generateFractal() {
for (let i = 0; i < axiom.length; i++) { for (let i = 0; i < axiom.length; i++) {
let c = axiom.charAt(i); let c = axiom.charAt(i);