added AI grebed presets and ugly collision check function
This commit is contained in:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user