JAVASCRIPT
// CFDG core functions
"use strict";
{
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
let width, height, scale, offsetX, offsetY, minSize, rect, seed, iter;
const drawCalls = [];
const transforms = {
x(m, v) {
m[4] += v * m[0];
m[5] += v * m[1];
},
y(m, v) {
m[4] += v * m[2];
m[5] += v * m[3];
},
z(m, v) {
m[10] += v;
},
s(m, v) {
const a = Array.isArray(v);
const x = a ? v[0] : v;
const y = a ? v[1] : x;
m[0] *= x;
m[1] *= x;
m[2] *= y;
m[3] *= y;
},
r(m, v) {
const rad = Math.PI * v / 180;
const cos = Math.cos(rad);
const sin = Math.sin(rad);
const r00 = cos * m[0] + sin * m[2];
const r01 = cos * m[1] + sin * m[3];
m[2] = cos * m[2] - sin * m[0];
m[3] = cos * m[3] - sin * m[1];
m[0] = r00;
m[1] = r01;
},
f(m, v) {
const rad = Math.PI * v / 180;
const x = Math.cos(rad);
const y = Math.sin(rad);
const n = 1 / (x * x + y * y);
const b00 = (x * x - y * y) / n;
const b01 = 2 * x * y / n;
const b10 = 2 * x * y / n;
const b11 = (y * y - x * x) / n;
const r00 = b00 * m[0] + b01 * m[2];
const r01 = b00 * m[1] + b01 * m[3];
m[2] = b10 * m[0] + b11 * m[2];
m[3] = b10 * m[1] + b11 * m[3];
m[0] = r00;
m[1] = r01;
},
skew(m, v) {
const x = Math.tan(Math.PI * v[0] / 180);
const y = Math.tan(Math.PI * v[1] / 180);
const r00 = m[0] + y * m[2];
const r01 = m[1] + y * m[3];
m[2] = x * m[0] + m[2];
m[3] = x * m[1] + m[3];
m[0] = r00;
m[1] = r01;
},
hue(m, v) {
m[6] += v;
m[6] %= 360;
},
sat(m, v) {
this.col(m, v, 7);
},
b(m, v) {
this.col(m, v, 8);
},
a(m, v) {
this.col(m, v, 9);
},
col(m, v, p) {
if (v > 0) {
m[p] += v * (1 - m[p]);
} else {
m[p] += v * m[p];
}
},
raf(m, v) {
m[12] = v;
}
};
const PRIMITIVES = [
// SQUARE
s => {
setTransform(s);
ctx.fillStyle = hsla(s);
ctx.fillRect(-0.5, -0.5, 1, 1);
},
// CIRCLE
s => {
setTransform(s);
ctx.fillStyle = hsla(s);
ctx.beginPath();
ctx.arc(0, 0, 0.5, 0, 2 * Math.PI);
ctx.fill();
}
];
const drawPush = (s, t, p) => {
s = transform(s, t);
bbox(s);
s[11] = p;
drawCalls.push(s);
};
const SQUARE = (s, t) => drawPush(s, t, 0);
const CIRCLE = (s, t) => drawPush(s, t, 1);
const copy = s => {
return [
s[0], // a00
s[1], // a10
s[2], // a01
s[3], // a11
s[4], // tx
s[5], // ty
s[6], // hue
s[7], // saturation
s[8], // brillance
s[9], // alpha
s[10], // zIndex
s[11] // primitive
];
};
const transform = (s, p) => {
let m = copy(s);
for (const c in p) {
transforms[c](m, p[c]);
}
return m;
};
const random = _ => {
seed = (seed * 16807) % 2147483647;
return (seed - 1) / 2147483646;
};
const randint = (s, e = 0) => {
if (e === 0) {
e = s;
s = 0;
}
return Math.floor(s + random() * (e - s + 1));
};
const loop = (n, s, t, f) => {
let ls = copy(s);
for (let i = 0; i < n; i++) {
f(ls, i);
ls = transform(ls, t);
}
};
const setTransform = s => {
ctx.setTransform(
-scale * s[0],
scale * s[1],
scale * s[2],
-scale * s[3],
scale * s[4] + offsetX,
-scale * s[5] + offsetY
);
};
const tooSmall = s => {
const x = (s[0] * s[0] + s[1] * s[1]) * scale;
const y = (s[2] * s[2] + s[3] * s[3]) * scale;
return x < minSize && y < minSize;
};
const hsla = m => {
let s = m[7];
const h = m[6];
const v = m[8];
const a = m[9];
// hsv to hsl
const l = (2 - s) * v / 2;
if (l != 0) {
if (l == 1) {
s = 0;
} else if (l < 0.5) {
s = s * v / (l * 2);
} else {
s = s * v / (2 - l * 2);
}
}
return `hsla(${Math.round(h)},${s * 100}%,${l * 100}%,${a})`;
};
const bbox = s => {
const x = s[4] * scale;
const y = s[5] * scale;
if (x < rect[0]) rect[0] = x;
else if (x > rect[1]) rect[1] = x;
if (y < rect[2]) rect[2] = y;
else if (y > rect[3]) rect[3] = y;
};
const autoScale = s => {
const ns =
Math.min(width / (rect[1] - rect[0]), height / (rect[3] - rect[2])) * s;
scale *= ns;
offsetX = width * 0.5 - (rect[0] + rect[1]) * 0.5 * ns;
offsetY = height * 0.5 + (rect[3] + rect[2]) * 0.5 * ns;
};
const init = (background, m) => {
width = canvas.width = canvas.offsetWidth * 2;
height = canvas.height = canvas.offsetHeight * 2;
ctx.fillStyle = background;
ctx.fillRect(0, 0, width, height);
scale = 100;
minSize = m;
rect = [0, 0, 0, 0];
drawCalls.length = 0;
};
const next = _ => {
requestAnimationFrame(_ => iter.next());
};
const run = function*() {
for (const s of drawCalls) {
PRIMITIVES[s[11]](s);
if(s[12]) yield next();
}
yield next();
};
const render = _ => {
for (let i = 0, len = drawCalls.length; i < len; i++) {
let j = i, item = drawCalls[j];
for(; j > 0 && drawCalls[j - 1][10] < item[10]; j--) {
drawCalls[j] = drawCalls[j - 1];
}
drawCalls[j] = item;
}
iter && iter.return();
iter = run();
iter.next();
};
["click", "touchdown"].forEach(event => {
document.addEventListener(event, e => START(), false);
});
//////////////////////////////////////////////////////////////////
// Adapted from a CFDG program
// https://www.contextfreeart.org/gallery2/index.html#design/237
// Sunset City by chris, March 16th, 2006
//////////////////////////////////////////////////////////////////
seed = Math.round(Math.random() * 1000000);
let numBuildings = 0;
const START = _ => {
init("#000", 1);
numBuildings = 0;
cityblock([1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0]);
autoScale(1);
offsetY = 1900 * (height / 2048);
render();
};
const cityblock = (s, t) => {
s = transform(s, t);
if (random() * 1.1 <= 0.1 && numBuildings > 12) {
return;
}
numBuildings += 4;
building(s, { x: 0, z: randint(10000)});
building(s, { x: 10, z: randint(10000)});
building(s, { x: 20, z: randint(10000)});
building(s, { x: 30, z: randint(10000)});
return cityblock(s, { x: 3, y: -0.1, s: 1.1 });
};
const building = (s, t) => {
s = transform(s, t);
const r = random() * 1002;
switch (true) {
case r <= 1:
building_1(s);
return building_1(s, { x: 5, b: -1 });
case r <= 2:
return building_2(s, { x: 3 });
default:
return building(s, { r: random() > 0.5 ? 0.1 : -0.1 });
}
};
const building_1 = (s, t) => {
s = transform(s, t);
if (random() <= 0.1) {
return SQUARE(s, { x: -0.75, y: 1, s: 3, skew: [-26.5, 0], b: -1, raf: 1 });
}
building_side_layer_1(s);
building_side_layer_1(s, {
x: -2.25,
y: 1.5,
s: [0.5, 1],
skew: [0, -45],
b: 1,
hue: 30
});
return building_1(s, { y: 1, b: 0.1, r: random() > 0.5 ? 0.05 : -0.05 });
};
const building_side_layer_1 = (s, t) => {
s = transform(s, t);
SQUARE(s, { s: [3, 1], sat: 1 });
random_window_1(s, { x: -1, s: [0.3, 0.5], hue: 50, sat: 1 });
random_window_1(s, { x: 0, s: [0.3, 0.5], hue: 50, sat: 1 });
random_window_1(s, { x: 1, s: [0.3, 0.5], hue: 50, sat: 1 });
};
const random_window_1 = (s, t) => {
s = transform(s, t);
const r = random() * 1.2;
switch (true) {
case r <= 0.1:
return SQUARE(s);
case r <= 0.2:
return random_window_1(s, { b: 1 });
default:
return random_window_1(s, { b: -0.5 });
}
};
const building_2 = (s, t) => {
s = transform(s, t);
if (random() <= 0.1) {
if (random() > 0.5) return building_top_2(s);
else {
building_top_2(s);
return building_1(s, { x: -0.75, y: 1 });
}
return;
}
building_side_layer_2(s);
building_side_layer_2(s, {
x: -3.75,
y: 2.5,
s: [0.5, 1],
skew: [0, -45],
b: 0.6,
hue: 0
});
return building_2(s, { y: 1, b: 0.01, r: random() > 0.5 ? 0.1 : -0.1 });
};
const building_top_2 = (s, t) => {
s = transform(s, t);
SQUARE(s, { x: -1.25, y: 2, s: 5, skew: [-26.5, 0], b: -0.9, raf: 1 });
};
const building_side_layer_2 = (s, t) => {
s = transform(s, t);
SQUARE(s, { s: [5, 1], sat: 1 });
random_window_2(s, { x: -2, s: 0.5, hue: 50, sat: 1 });
random_window_2(s, { x: -1, s: 0.5, hue: 50, sat: 1 });
random_window_2(s, { x: 0, s: 0.5, hue: 50, sat: 1 });
random_window_2(s, { x: 1, s: 0.5, hue: 50, sat: 1 });
random_window_2(s, { x: 2, s: 0.5, hue: 50, sat: 1 });
};
const random_window_2 = (s, t) => {
s = transform(s, t);
const r = random() * 1.3;
switch (true) {
case r <= 0.1:
return SQUARE(s);
case r <= 0.2:
return random_window_2(s, { b: 1 });
case r <= 0.3:
return random_window_2(s, { b: 0.1 });
default:
return random_window_2(s, { b: -1 });
}
};
START();
}