mikewlange
8/6/2019 - 2:49 AM

Globe

Globe

(function () {
  'use strict';

  // braille

  const enc = x => (x&0x08)<<3 | (x&0x70)>>1 | (x&0x87) | 0x2800;
  const row = x => String.fromCharCode(...Array.from(x, enc));

  const create = (width, height) =>
    Array.from(Array(height>>2), () => new Uint8Array(width>>1));

  const set = (table, x, y) =>
    table[y>>2][x>>1] |= 1<<((y&3)|(x&1)<<2);

  const render = table =>
    table.map(row).join('\n');

  // util

  const frame = () =>
    new Promise(resolve => requestAnimationFrame(resolve));

  const element = (name, options) =>
    Object.assign(document.createElement(name), options);

  // bayer

  const bayer = (order, x, y) => {
    let z = 0;
    for (let i = order; i--; x >>= 1, y >>= 1)
      z = (x&1 ^ y&1 | z<<1)<<1 | y&1;
    return z
  };

  const lut = order => {
    const size = 1<<order, area = size*size;
    const lut = new Float32Array(area);
    for (let y = 0; y < size; y++)
      for (let x = 0; x < size; x++)
        lut[x + y*size] = (bayer(order, x, y) + .5) / area;
    return (x, y) => lut[x%size + y%size*size]
  };

  // render

  const simplex = new SimplexNoise;
  const bayer4 = lut(4);

  const fbm = (freq, amp, x, y, z) =>
    simplex.noise3D(x*(freq*=2), y*freq, z*freq) * (amp/=2) +
    simplex.noise3D(x*(freq*=2), y*freq, z*freq) * (amp/=2) +
    simplex.noise3D(x*(freq*=2), y*freq, z*freq) * (amp/=2) +
    simplex.noise3D(x*(freq*=2), y*freq, z*freq) * (amp/=2) +
    simplex.noise3D(x*(freq*=2), y*freq, z*freq) * (amp/=2) +
    simplex.noise3D(x*(freq*=2), y*freq, z*freq) * (amp/=2) +
    simplex.noise3D(x*(freq*=2), y*freq, z*freq) * (amp/=2) +
    simplex.noise3D(x*(freq*=2), y*freq, z*freq) * (amp/=2);

  const texture = (u, v, w) =>
    2*(.5 + .5*fbm(.5, 1, u, v, w)) % 1;

  const globe = (x, y, u, v, w) => {
    const d = u*u + v*v;
    if (d > 1) return false

    const f = 1 / ((1 - d**.5)**.5 + 1);
    const t = texture(1e-1*w + f*u, f*v, 1e-2*w);
    return t > bayer4(x, y)
  };

  // main

  const main = async () => {
    const fillerSize = 100;
    const filler = '\u28ff'.repeat(fillerSize) + '\n\u28ff'.repeat(fillerSize - 1);

    const root = element('div', { className: 'braille' });
    const hidden = element('div', { className: 'hidden', textContent: filler });
    const visible = element('div', { className: 'visible' });

    root.appendChild(hidden);
    root.appendChild(visible);
    document.body.appendChild(root);

    for (;; await frame()) {
      const hr = hidden.getBoundingClientRect();
      const fontWidth = hr.width/fillerSize;
      const fontHeight = hr.height/fillerSize;

      const rr = root.getBoundingClientRect();
      const ratio = rr.width/rr.height;
      const width = rr.width/fontWidth << 1;
      const height = rr.height/fontHeight << 2;

      const pixels = create(width, height);
      const time = 1e-3*Date.now();

      for (let y = 0; y < height; y++) {
        const v = 2*y/height - 1;
        for (let x = 0; x < width; x++) {
          const u = ratio*(2*x/width - 1);
          if (globe(x, y, u, v, time))
            set(pixels, x, y);
        }
      }

      visible.textContent = render(pixels);
    }
  };

  main();

}());
<script src="//unpkg.com/simplex-noise@2"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tether/1.4.4/js/tether.min.js"></script>
@import url('//cdn.rawgit.com/arcticicestudio/nord/develop/src/nord.css');
@import url('//cdn.jsdelivr.net/gh/be5invis/Iosevka@ff81c66/fonts.css');

:root {
  --size: calc(100vmin / 1.618033988749895);
  --background: var(--nord0);
  --foreground: var(--nord6);
}

html, body {
  width: 100%;
  height: 100%;
}

body {
  margin-left: -20%;
  background: var(--background);
  color: var(--foreground);
}

body, .braille {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-flow: row;
}

.braille {
  max-width: 100%;
  max-height: 100%;
  width: var(--size);
  height: var(--size);
  font: 11px/12px 'Iosevka Web', monospace;
}

.braille > * {
  white-space: pre;
}

.braille .hidden {
  position: fixed;
  bottom: 100%;
  right: 100%;
}