xxxxxxxxxx
201
/*
Amazing explanation (+ inspiration) by 3 Blue One Brown:
https://www.youtube.com/watch?v=3s7h2MHQtxc
https://www.youtube.com/watch?v=RU0wScIj36o
Curve reference:
https://en.wikipedia.org/wiki/L-system
https://mathworld.wolfram.com/HilbertCurve.html
This script can render avi's and gifs. Setup recording in setup().
Press enter to start and stop recording.
Will also stop recording after full cycle. Use this for perfect loops :)
Feel free to share your gifs on my discord server!
https://discord.com/invite/DxbUee2y6D
(max upload 8mb)
recommended gif settings:
canvas size: 500x500
quality: 1 (highest for 'accurate' colours)
fps: 30 (or lower to save space)
frames: 500
libraries used:
Video Builder: https://github.com/theshock/VideoBuilder
gif.js: https://github.com/jnordberg/gif.js
download: https://github.com/rndme/download
*/
let rules = [{ // Koch curve
rewrite: {
"F": "F+F-F-F+F",
},
start: "F",
angle: 1 / 4,
depth: 5,
},
{ // Sierpiński triangle
rewrite: {
F: "FF",
G: "-FGF+FGF+FGF-"
},
start: "G",
angle: 1 / 3,
lineChars: ["F"],
stepWhenFractal: true,
depth: 6,
},
{ // Sierpiński arrowhead curve
rewrite: {
A: "B-A-B",
B: "A+B+A",
},
start: "A",
angle: 1 / 6,
depth: 8,
},
{ // Dragon curve
rewrite: {
F: "F+G",
G: "F-G",
},
start: "F",
angle: 1 / 4,
depth: 13,
},
{ // Hilbert curve
rewrite: {
L: "+RF-LFL-FR+",
R: "-LF+RFR+FL-",
},
start: "R",
angle: 1 / 4,
lineChars: ["F"],
depth: 7,
}
]
function lsystem(rule, str, x, y, a, depth) {
const { rewrite, angle, lineChars, stepWhenFractal } = rule;
[str].forEach(c => {
switch (c) {
case "+":
a += angle;
break;
case "-":
a -= angle;
break;
default:
const nStr = rewrite[c];
const fractal = depth > 1 && nStr;
if (fractal)
({x, y, a } = lsystem(rule, nStr, x, y, a, depth - 1));
else{
if (lineChars == null || lineChars.includes(c)) {
let nx = x + cos(a * TWO_PI)
let ny = y + sin(a * TWO_PI)
lines.push({x1: x, y1: y, x2: nx, y2: ny });
x = nx;
y = ny;
}
}
}
});
return { x, y, a };
}
let rec;
const frames = 3500;
// const frames = 500;
let frame = 0;
let lines;
function setup() {
const size = min(windowWidth, windowHeight); // fill window
// const size = 1080; // avi recording
// const size = 500; // gif recording
const canvas = createCanvas(size, size);
colorMode(HSL, 1);
stroke(1)
rec = setupRecording("l-system-fractal", canvas, {
// avi: {
// fps: 60,
// quality: 0.92 // full quality (1) does not encode properly (sad face)
// },
// gif: {
// fps: 60,
// quality: 1, // lower is better. Works to reduce filesize but fucks with colour, would recommend lowering fps instead ✌️
// workers: 10 // multithreaded workers
// }
});
// would recommend setting frames to 500 when rendering a single cruve ✌️
// rules = [rules[0]]; // just koch curve
// rules = [rules[1]]; // just sierpinski triangle
// rules = [rules[2]]; // just sierpinski arrowhead curve
// rules = [rules[3]]; // just dragon cruve
// rules = [rules[4]]; // just hilbert curve
}
function tri(v) {
return 1 - abs(1 - fract(v) * 2);
}
function invCosn(v) {
return 1 - (cos(v * TWO_PI) * 0.5 + 0.5);
}
function draw() {
let inc = 1;
if (!rec.recording)
inc = deltaTime / (1000 / 60);
frame += inc;
let t = fract(frame / frames);
// t = fract(mouseX / width);
const ruleIndex = floor(t * rules.length);
rule = rules[ruleIndex];
const sig = invCosn(t * rules.length);
const maxDepth = 1 + rule.depth * sig;
lines = [];
lsystem(rule, rule.start, 0, 0, 0, maxDepth);
const { minX, maxX, minY, maxY } = getBounds(lines);
const w = maxX - minX;
const h = maxY - minY;
const s = max(w, h);
scale(0.9 * width / s, 0.9 * height / s);
translate(
0.05 * s - minX + ((s - w) * 0.5),
0.05 * s - minY + ((s - h) * 0.5)
);
strokeWeight(0.005 * s);
background(0);
stroke(1);
lines.forEach(({ x1, y1, x2, y2 }, i) => {
line(x1, y1, x2, y2);
})
if (rec.recording)
rec.recordFrame();
}
function getBounds(lines) {
return {
minX: lines.reduce((minX, { x1, x2 }) => Math.min(minX, x1, x2), Number.MAX_SAFE_INTEGER),
minY: lines.reduce((minY, { y1, y2 }) => Math.min(minY, y1, y2), Number.MAX_SAFE_INTEGER),
maxX: lines.reduce((maxX, { x1, x2 }) => Math.max(maxX, x1, x2), Number.MIN_SAFE_INTEGER),
maxY: lines.reduce((maxY, { y1, y2 }) => Math.max(maxY, y1, y2), Number.MIN_SAFE_INTEGER),
};
}