Code
Fabric of Reality
An interactive WebGL experiment exploring the fabric of reality through animated particle systems.
CodePen Embed - Forking The Fabric of Reality
<canvas></canvas>
<!-- Click to generate a new image -->
html, body {
overflow: hidden;
touch-action: none;
content-zooming: none;
position: absolute;
margin: 0;
padding: 0;
width: 100%;
height: 100%;
background: #fff;
}
canvas {
position: absolute;
width: 100%;
height: 100%;
user-select: none;
cursor: pointer;
}
"use strict";
const cfa = {
canvas: document.querySelector("canvas"),
stack: [],
draws: [],
ajustments: {
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[11] += v;
},
rotate(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;
},
flip(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;
},
scale(m, v) {
let x, y;
if (Array.isArray(v)) {
x = v[0];
y = v[1];
} else {
x = v;
y = x;
}
m[0] *= x;
m[1] *= x;
m[2] *= y;
m[3] *= y;
},
hue(m, v) {
m[6] += v;
m[6] %= 360;
},
sat(m, v) {
this.adjustColor(m, v, 7);
},
bri(m, v) {
this.adjustColor(m, v, 8);
},
alpha(m, v) {
this.adjustColor(m, v, 9);
},
adjustColor(m, v, p) {
if (v > 0) {
m[p] += v * (1 - m[p]);
} else {
m[p] += v * m[p];
}
},
raf(m, v) {
m[10] = v ? 1 : 0;
},
},
adjust(s, p) {
const m = [s[0], s[1], s[2], s[3], s[4], s[5], s[6], s[7], s[8], s[9], s[10], s[11]];
for (const c in p) {
this.ajustments[c](m, p[c]);
}
return m;
},
setTransform(s) {
this.ctx.setTransform(
-this.scale * s[0],
this.scale * s[1],
this.scale * s[2],
-this.scale * s[3],
this.scale * s[4] + this.offsetX,
-this.scale * s[5] + this.offsetY
);
},
CIRCLE(s, p = null) {
p && (s = this.adjust(s, p));
this.draws.push([s, "circle"]);
this.boundingRect(s);
},
SQUARE(s, p = null) {
p && (s = this.adjust(s, p));
this.draws.push([s, "square"]);
this.boundingRect(s);
},
circle(s) {
this.setTransform(s);
this.fillStyle(s);
this.ctx.beginPath();
this.ctx.arc(0, 0, 0.5, 0, 2 * Math.PI);
this.ctx.fill();
},
square(s) {
this.setTransform(s);
this.fillStyle(s);
this.ctx.fillRect(-0.5, -0.5, 1, 1);
},
fillStyle(s) {
this.ctx.fillStyle = `hsla(${Math.round(s[6])},${Math.round(s[7] * 100)}%,${Math.round(
s[8] * 100
)}%,${s[9]})`;
},
boundingRect(s) {
const x = s[4] * this.scale;
const y = s[5] * this.scale;
if (x < this.rect[0]) this.rect[0] = x;
else if (x > this.rect[1]) this.rect[1] = x;
if (y < this.rect[2]) this.rect[2] = y;
else if (y > this.rect[3]) this.rect[3] = y;
},
center(s) {
const br = this.rect;
const scale =
Math.min(this.width / (br[1] - br[0]), this.height / (br[3] - br[2])) * s;
this.scale *= scale;
this.offsetX = this.width * 0.5 - (br[0] + br[1]) * 0.5 * scale;
this.offsetY = this.height * 0.5 + (br[3] + br[2]) * 0.5 * scale;
},
*render() {
let s = 0;
for (const draw of this.draws) {
this[draw[1]](draw[0]);
if (s++ > this.speed && draw[0][10]) {
s = 0;
yield requestAnimationFrame(_ => this.iter.next());
}
}
},
call(name, s, p) {
const x = (s[0] * s[0] + s[1] * s[1]) * this.scale;
const y = (s[2] * s[2] + s[3] * s[3]) * this.scale;
if (x < this.minSize && y < this.minSize) {
return;
}
s = this.adjust(s, p);
this.stack.push([name, s]);
},
run(code) {
for (const rule in code) {
this[rule] = code[rule];
}
this.ctx = this.canvas.getContext("2d");
this.width = this.canvas.width = this.canvas.offsetWidth * 2;
this.height = this.canvas.height = this.canvas.offsetHeight * 2;
this.ctx.fillStyle = this.setup.background || "#000";
this.ctx.fillRect(0, 0, this.width, this.height);
this.rect = [0, 0, 0, 0];
this.stack.length = 0;
this.draws.length = 0;
this.scale = 100;
this.speed = this.setup.speed || 100;
this.minSize = this.setup.minSize || 1.0;
this.call(this.setup.start, [1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0], false);
const t1 = performance.now();
let k = 0;
do {
k++;
const s = this.stack.shift();
this[s[0]](s[1]);
} while (this.stack.length);
const t2 = performance.now();
console.log(k, t2 - t1);
this.center(this.setup.zoom || 1.0);
this.iter && (this.iter.return());
this.iter = this.render();
this.iter.next();
}
};
["click", "touchdown"].forEach(event => {
document.addEventListener(event, e => cfa.run(code), false);
});
/////////////////////////////////////////////
const code = {
setup: {
background: "#fff",
minSize: 0.01,
zoom: 0.42,
speed: 3,
start: "start"
},
start(s) {
this.call("pyramid", s, {
hue: Math.random() * 360,
sat: 0.85
});
},
pyramid(s) {
this.SQUARE(s, { bri: -1, alpha: -0.97, scale: 1.3, raf: false});
this.SQUARE(s, { bri: -1, scale: 1.01, raf: false });
this.SQUARE(s, { bri: 0.8, raf: true });
this.call("pyramid", s, {
flip: (Math.random() - Math.random()) * 6,
hue: Math.random() * 14 - 7,
x: Math.random() * 0.14 - 0.07,
scale: 0.99
});
}
};
cfa.run(code);
Forking The Fabric of Reality
External CSS
This Pen doesn't use any external CSS resources.
External JavaScript
This Pen doesn't use any external JavaScript resources.