const palette = ['#a3c4bc', '#d2a189', '#bfd7b5','#e7efc5','#ffa69e'];
const paletteBgs = ['#413c58', '#483333', '#4C574C'];
let s = min(windowWidth, windowHeight);
cellWidth = (width-2*margin)/cellsX;
cellHeight = (height-2*margin)/cellsY;
generateAlphabet(PARAMS.letters);
const pane = new Tweakpane.Pane();
pane.addInput(PARAMS, 'showAlphabet').on('change', draw);
pane.addInput(PARAMS, 'useAlphabet').on('change', draw);
pane.addInput(PARAMS, 'letters', {min: 2, max: 200, step: 1}).on('change', function(ev) {
generateAlphabet(ev.value);
generateAlphabet(PARAMS.letters);
background(paletteBgs[randi(0, paletteBgs.length)]);
stroke(palette[randi(0, palette.length)]);
strokeWeight(cellWidth/30);
if (!PARAMS.showAlphabet) {
translate(margin, margin);
for(let row = 0; row < cellsY; row++) {
for (let col = 0; col < cellsX; col++) {
translate(row*cellHeight, col*cellWidth)
translate(cellWidth/2, cellHeight/2);
if(PARAMS.useAlphabet) KnotGlyph(knots[randi(0, knots.length)]);
drawFrame(margin/2, margin/2, width-margin, height-margin);
translate(margin, margin);
for (let col = cellsX/2-sqrt(PARAMS.letters)/2; col < cellsX/2+sqrt(PARAMS.letters)/2; col++) {
for(let row = cellsY/2-sqrt(PARAMS.letters)/2; row < cellsY/2+sqrt(PARAMS.letters)/2; row++) {
if(i >= knots.length) break;
translate(row*cellHeight, col*cellWidth)
translate(cellWidth/2, cellHeight/2);
drawFrame(margin/2, margin/2, 0, 0);
function generateKnotParams() {
var rr = [], thetar = [], phir = [];
rr[0] = min(cellWidth, cellHeight) / 2;
thetar[0] = cellWidth / randi(5, 15);
thetar[1] = randi(2, 10);
phir[0] = cellWidth / randi(2, 8);
let knotParams = {rr: rr, thetar:thetar, phir: phir};
function KnotGlyph(knotParams = generateKnotParams()) {
let thetar = knotParams.thetar;
let phir = knotParams.phir;
let getPoint = function(t) {
let r = rr[0] * sin(rr[1] * t) + rr[2];
let theta = thetar[0] * sin(thetar[1] * t) + thetar[2];
let phi = phir[0] * sin(phir[1] * t) + phir[2];
x: r * cos(phi) * cos(theta),
y: r * cos(phi) * sin(theta),
for(let i = 0; i < N; i++) {
curveVertex(p.x, p.y, p.z);
function generateAlphabet(N) {
for(let i = 0; i < N; i++) {
knots[i] = generateKnotParams();
function drawFrame(x, y, w, h) {
translate(width/2, height/2);
for(let i = 0; i < 4; i++) {
drawQuarterFrame(x, y, w, h);
function drawQuarterFrame(x, y, w, h) {
vertex(-width/2+x+b, -height/2+y);
vertex(-width/2+x+b, -height/2+y+2*b);
vertex(-width/2+x+2*b, -height/2+y+2*b);
vertex(-width/2+x+2*b, -height/2+y+b);
vertex(-width/2+x, -height/2+y+b);
return random(0, 100) < x
function randi(min, max) {
return floor(random(min, max));
save("screenshots/" + Date.now() + ".png");