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.