/* L-System Fractal Generator The following characters have a geometric interpretation: Character Meaning F Move forward by line length drawing a line f Move forward by line length without drawing a line + Turn left by turning angle - Turn right by turning angle | Reverse direction (ie: turn by 180 degrees) [ Push current drawing state onto stack ] Pop current drawing state from the stack # Increment the line width by line width increment ! Decrement the line width by line width increment @ Draw a dot with line width radius { Open a polygon } Close a polygon and fill it with fill colour > Multiply the line length by the line length scale factor < Divide the line length by the line length scale factor & Swap the meaning of + and - ( Decrement turning angle by turning angle increment ) Increment turning angle by turning angle increment */ let angle = Math.PI * 1.5; let turningAngle = Math.PI * 0.5; let lineLength = 5; let lineWidthIncrement = 1; let currentLineWidth = 1; let currentPosition = [0, 0]; let stateStack = []; let lineLengthScaleFactor = 1.5; let swapPlusMinusState = false; let turningAngleIncrement = 0.1; let polygonVertices = []; let polygonOpen = false; let axiom = 'F+F+F+F'; let maxIterations = 4; const maxCalculations = 50000; let rules = { 'F': 'FF+F-F+F+FF' }; // 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; } function setup() { createCanvas(windowWidth, windowHeight); background(255, 255, 255); noFill(); stroke(0, 0, 0); strokeWeight(currentLineWidth); currentPosition = [width / 2, height - 100]; generateFractal(); } function generateFractal() { for (let i = 0; i < axiom.length; i++) { let c = axiom.charAt(i); switch (c) { case "F": moveForwardDraw(); break; case "f": moveForwardNoDraw(); break; case "+": turnLeft(); break; case "-": turnRight(); break; case "|": reverseDirection(); break; case "[": pushState(i); break; case "]": popState(i); break; case "#": incrementLineWidth(); break; case "!": decrementLineWidth(); break; case "@": drawDot(); break; case "{": openPolygon(); break; case "}": closePolygon(); break; case ">": multiplyLineLength(); break; case "<": divideLineLength(); break; case "&": swapPlusMinus(); break; case ")": incrementTurningAngle(); break; case "(": decrementTurningAngle(); break; default: // Ignore characters not in the specification (like X) break; } } } // F: Move forward by line length drawing a line function moveForwardDraw() { let x = currentPosition[0] + cos(angle) * lineLength; let y = currentPosition[1] + sin(angle) * lineLength; if (polygonOpen) { polygonVertices.push([x, y]); } else { line(currentPosition[0], currentPosition[1], x, y); } currentPosition = [x, y]; } // f: Move forward by line length without drawing a line function moveForwardNoDraw() { let x = currentPosition[0] + cos(angle) * lineLength; let y = currentPosition[1] + sin(angle) * lineLength; currentPosition = [x, y]; } // +: Turn left by turning angle function turnLeft() { if (swapPlusMinusState) { angle -= turningAngle; return } angle += turningAngle; } // -: Turn right by turning angle function turnRight() { if (swapPlusMinusState) { angle += turningAngle; return } angle -= turningAngle; } // |: Reverse direction (turn by 180 degrees) function reverseDirection() { angle += Math.PI; } // [: Push current drawing state onto stack function pushState(i) { stateStack.push([i, currentPosition[0], currentPosition[1], angle, currentLineWidth]); } // ]: Pop current drawing state from the stack function popState(i) { let stackIndex = stateStack.length - 1; currentPosition = [stateStack[stackIndex][1], stateStack[stackIndex][2]]; angle = stateStack[stackIndex][3]; currentLineWidth = stateStack[stackIndex][4]; strokeWeight(currentLineWidth); } // #: Increment the line width by line width increment function incrementLineWidth() { currentLineWidth += lineWidthIncrement; strokeWeight(currentLineWidth); } // !: Decrement the line width by line width increment function decrementLineWidth() { currentLineWidth -= lineWidthIncrement; strokeWeight(currentLineWidth); } // @: Draw a dot with line width radius function drawDot() { let x = currentPosition[0]; let y = currentPosition[1]; strokeWeight(currentLineWidth); point(x, y); } // {: Open a polygon function openPolygon() { polygonVertices = [[currentPosition[0], currentPosition[1]]]; polygonOpen = true; } // }: Close a polygon and fill it with fill colour function closePolygon() { if (polygonOpen && polygonVertices.length > 2) { fill(100, 150, 255); // Default fill colour beginShape(); for (let v of polygonVertices) { vertex(v[0], v[1]); } endShape(CLOSE); noFill(); // Reset to no fill } polygonVertices = []; polygonOpen = false; } // >: Multiply the line length by the line length scale factor function multiplyLineLength() { lineLength *= lineLengthScaleFactor; } // <: Divide the line length by the line length scale factor function divideLineLength() { lineLength /= lineLengthScaleFactor; } // &: Swap the meaning of + and - function swapPlusMinus() { swapPlusMinusState = !swapPlusMinusState; } // ): Increment turning angle by turning angle increment function incrementTurningAngle() { turningAngle += turningAngleIncrement; } // (: Decrement turning angle by turning angle increment function decrementTurningAngle() { turningAngle -= turningAngleIncrement; } function draw() { } function mousePressed() { }