From 9ba379ed29a3aecb0b103ee7c2d6f8bb63f13a94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrg=20Hallenbarter?= Date: Mon, 27 Apr 2026 18:59:48 +0000 Subject: [PATCH] added AI grebed presets and ugly collision check function --- sketches/L-System.js | 212 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 194 insertions(+), 18 deletions(-) diff --git a/sketches/L-System.js b/sketches/L-System.js index 3cea30a..e262bb9 100644 --- a/sketches/L-System.js +++ b/sketches/L-System.js @@ -1,6 +1,8 @@ /* L-System Fractal Generator +https://paulbourke.net/fractals/lsys/ + The following characters have a geometric interpretation: Character Meaning @@ -40,25 +42,51 @@ let axiom = 'F+F+F+F'; let maxIterations = 4; const maxCalculations = 50000; let rules = { - 'F': 'FF+F-F+F+FF' + 'F': 'FF+F++F+F' }; -// Generate the fractal string from axiom and rules -for (let i = 0; i < maxIterations; i++) { - if (axiom.length > maxCalculations) {//hard coded limit to prevent infinite loops - console.log('Reached max calculations, stopping at iteration', i); - break; - } - let newAxiom = ''; - for (let char of axiom) {//loop through each character in the axiom and replace it with the corresponding rule - if (rules[char]) { - newAxiom += rules[char]; - } else { - newAxiom += char; - } - } - axiom = newAxiom; -} +//* L-System presets array **AI generated** +const presets = [ + { name: 'Crystal', axiom: 'F+F+F+F', rules: { 'F': 'FF+F++F+F' }, angle: 90 }, + { name: 'Koch Curve', axiom: 'F+F+F+F', rules: { 'F': 'F+F-F-FF+F+F-F' }, angle: 90 }, + { 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 }, + { name: 'Dragon Curve', axiom: 'FX', rules: { 'X': 'X+YF+', 'Y': '-FX-Y' }, angle: 90 }, + { name: 'Levy Curve', axiom: 'F', rules: { 'F': '-F++F-' }, angle: 45 }, + { name: 'Sierpinski Arrowhead', axiom: 'YF', rules: { 'X': 'YF+XF+Y', 'Y': 'XF-YF-X' }, angle: 60 }, + { name: 'Peano Curve', axiom: 'X', rules: { 'X': 'XFYFX+F+YFXFY-F-XFYFX', 'Y': 'YFXFY-F-XFYFX+F+YFXFY' }, angle: 90 }, + { name: 'Plant', axiom: 'X', rules: { 'X': 'F+[[X]-X]-F[-FX]+X', 'F': 'FF' }, angle: 25 }, + { 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 }, + { 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() { createCanvas(windowWidth, windowHeight); @@ -66,10 +94,158 @@ function setup() { noFill(); stroke(0, 0, 0); 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(); } + +// * 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() { for (let i = 0; i < axiom.length; i++) { let c = axiom.charAt(i);