<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">

  <title><![CDATA[Acko.net]]></title>
  <link href="https://acko.net/atom.xml" rel="self"/>
  <link href="https://acko.net/"/>
  <updated>2026-03-05T12:10:39+01:00</updated>
  <id>https://acko.net</id>
  <author>
    <name><![CDATA[Steven Wittens]]></name>
    
  </author>

  
  <entry>
    <title type="html"><![CDATA[Yak Shading]]></title>
    <link href="https://acko.net/blog/yak-shading/"/>
    <updated>2015-09-27T00:00:00+02:00</updated>
    <id>https://acko.net/blog/yak-shading</id>
    <content type="html"><![CDATA[<script src="/files/katex/katex.min.js"></script>

<script src="/files/katex/contrib/auto-render.min.js"></script>

<link rel="stylesheet" type="text/css" href="/files/katex/katex.min.css" />

<div class="g8 i2 first"><div class="pad">

<h2 class="sub">Data-Driven Geometry</h2>

<p>MathBox primitives need to take arbitrary data, transform it on the fly, and render it as styled geometry based on their attributes. Done as much as possible on the graphics hardware.</p>

<p>Three.js can render points, lines, triangles, but only with a few predetermined strategies. The alternative is to write your own vertex and fragment shader and do everything from scratch. Each new use case means a new <code>ShaderMaterial</code> with its own properties, so called <em>uniforms</em>. If the stock geometry doesn't suffice, you can make your own triangles by filling a raw <code>BufferGeometry</code> and assign custom per-vertex attributes. Essentially, to leverage GPU computation with Three.js—most engines, really—you have to ignore most of it.</p>


<h2>Virtual Geometry</h2>

<p>Shader computations are mainly rote transforms. For example, if you want to draw a line between two points, you'll have to make a long rectangle, made out of two triangles. But this simple idea gets complicated quickly once you add corner joins, depth scaling, 3D clipping, and so on. Doing this to an entire data set at once is what GPUs are made for, through vertex shaders which transform points.</p>

</div></div>

<div class="wide full">

  <div class="iframe c">
    <iframe src="/files/mathbox2/iframe-line-shader.html?c3d6624d" class="mathbox janky" height="150" style="width: 100%;"></iframe>
  </div>

</div>

<div class="c"></div>

<div class="wide m1">
   <img src="/files/mathbox2/yakshading2.png" class="flat" alt="Vertex Shader" style="position: relative; left: -5px;" />
</div>

<div class="g8 i2 m1"><div class="pad">
<p>Vertex shaders can only do 1-to-1 mappings. This isn't a problem by itself. You can use a <em>gather</em> approach to do N-to-1 mapping, where all the necessary data is pre-arranged into attribute arrays, with the data repeated and interleaved per vertex as necessary.</p>
</div></div>

<div class="c"></div>

<div class="wide m2">
   <img src="/files/mathbox2/yakshading3.png" class="flat" alt="Vertex Shader Attributes" style="position: relative; left: -5px;" />
</div>

<div class="g8 i2 m1"><div class="pad">
<p>The proper tool for this is a geometry shader: a program that creates new geometry by N-to-M mapping of data, like making triangles out of points. WebGL doesn't support geometry shaders, won't any time soon, but you can emulate them with texture samplers. A texture image is just a big typed array, and you have random access unlike vertex attributes.</p>

</div></div>

<div class="c"></div>

<div class="wide m2">
   <img src="/files/mathbox2/yakshading.png" class="flat" alt="Yak Shading" style="position: relative; left: -5px;" />
</div>

<div class="g8 i2 m1"><div class="pad">

<p>The original geometry acts only as a template, directing the shader's real data lookups. You lose some performance this way, but it's not too bad. Any procedural sampling pattern works, drawing 1 shape or 10,000. As textures can be rendered <em>to</em>, not just from, this also enables transform feedback, using the result of one pass to create new geometry in another.</p>

<p>All geometry rendered this way is 100% static as far as Three.js is concerned. New values are uploaded directly to GPU memory just before the rendering starts. The only gotcha is handling variable size input, because reallocation is costly. Pre-allocating a larger texture is easy, but clipping off the excess geometry in an O(1) fashion on the JS side is hard. In most cases there's the work around of dynamically generating <em>degenerate</em> triangles in a shader, which collapse down to invisible edges or points. This way, MathBox can accept variable sized arrays in multiple dimensions and will do its best to minimize disruption. If attribute instancing was more standard in WebGL, this wouldn't be such an issue, but as it stands, the workarounds are very necessary.</p>

</div></div>

<div class="wide full">

  <div class="iframe c">
    <iframe src="/files/mathbox2/iframe-cylindrical-stream.html?c3d6624d" class="mathbox autosize janky" height="320"></iframe>
  </div>

</div>

<div class="g8 i2"><div class="pad">

<h2>Vertex Party</h2>

<p>If you squint <em>very</em> hard it looks a bit like React for live geometry. Except instead of a diffing algorithm, there's a few events, some texture uploads, a handful of draw calls and then an idle CPU. It's ideal for drawing thousands of things that look similar and follow the same rules. It can handle not just basic GL primitives like lines or triangles, but higher level shapes like 3D arrows or sprites.</p>

<p>My first prototype of this was my last <a href="http://christmasexperiments.com/2013/22/" target="_blank">christmas demo</a>. It was messy and tedious to make, especially the shaders, but it performed excellently: the final scene renders ~200,000 triangles. Despite being a layer around Three.js … around WebGL … around OpenGL … around a driver … around a GPU … performance has far exceeded my expectations. Even complex scenes run great on my Android phone, easily 10x faster than MathBox 1, in some cases more like 1000x.</p>

<p>Of course compared to cutting edge DirectX or <a href="http://en.wikipedia.org/wiki/OpenCL">OpenCL</a> (not a typo), this is still very limited. In today's GPUs, the charade of attributes, geometries, vertices and samples has mostly been stripped away. What remains is buffers and massive operations on them, exposed raw in new APIs like AMD's Mantle and iOS's Metal. My vertex trickery acts like a polyfill, virtualizing WebGL's capabilities to bring them closer to the present. It goes a bit beyond what geometry shaders can provide, but still lacks many useful things like atomic append queues or stream compaction.</p>

<p>For large geometries, the set up cost can be noticeable though. Shader compilation time also grows with transform complexity, doubly so on Windows where shaders are recompiled to HLSL / Direct3D. This makes drawing ops the heaviest MathBox primitives to spawn and reallocate. You could call this the MathBox version of the dreaded 'paint' of HTML. Once warmed up though, most other properties can be animated instantly, including the data being displayed: this is the opposite of how HTML works. Hence you can mostly spawn things ahead of time, revealing and hiding objects as needed, with minimal overhead and jank at runtime.</p>

<p>This all relies on carefully constructed shaders which have to be wired up in all their individual permutations. This needed to be solved programmatically, which is where we go last.</p>

<ul>
  <li><a href="/blog/mathbox2/">MathBox² - PowerPoint Must Die</a></li>
  <li><a href="/blog/a-dom-for-robots/">A DOM for Robots - Modelling Live Data</a></li>
  <li><em>Yak Shading - Data Driven Geometry</em></li>
  <li><a href="/blog/shadergraph-2/">ShaderGraph 2 - Functional GLSL</a></li>
</ul>

</div></div>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Shader&shy;Graph 2]]></title>
    <link href="https://acko.net/blog/shadergraph-2/"/>
    <updated>2015-09-27T00:00:00+02:00</updated>
    <id>https://acko.net/blog/shadergraph-2</id>
    <content type="html"><![CDATA[<script src="/files/katex/katex.min.js"></script>

<script src="/files/katex/contrib/auto-render.min.js"></script>

<link rel="stylesheet" type="text/css" href="/files/katex/katex.min.css" />

<div class="g8 i2 first"><div class="pad">

<h2 class="sub">Functional GLSL</h2>

<p>For MathBox 1, I already needed to generate GL shaders programmatically. So I built ShaderGraph. You gave it snippets of GLSL code, each with a function inside. It would connect them for you, matching up the inputs and outputs. It supported directed graphs of calls with splits and joins, which were compiled down into a single shader. To help build up the graph progressively, it came with a simple chainable factory API.</p>

<p>It worked despite being several steps short of being a real compiler and having gaps in its functionality. It also committed the cardinal sin of regex code parsing, and hence accepted only a small subset of GLSL. All in all it was a bit of a happy mess, weaving vertex and fragment shaders together in a very ad-hoc fashion. Each snippet could only appear once in a shader, as it was still just a dumb code concatenator. I needed a proper way to compose shaders.</p>

</div></div>

<div class="c"></div>

<div class="wide full">

  <div class="iframe c">
    <iframe src="/files/mathbox2/iframe-shadergraph-1.html?c3d6624d" class="janky" height="190" style="width: 100%;"></iframe>
  </div>
  
  <p class="tc"><em><small>Select a node to view its code</small></em></p>

</div>

<div class="c"></div>

<div class="g8 i2"><div class="pad">

<h2 class="mt2">Instanced Data Flow</h2>

<p>Enter <a href="https://github.com/unconed/shadergraph" target="_blank">ShaderGraph 2</a>. It's a total rewrite using Chris Dickinson's bona fide <code>glsl-parser</code>. It still parses snippets and connects them into a directed graph to be compiled. But a snippet is now a full GLSL program whose <code>main()</code> function can have open inputs and outputs. What's more, it now also <em>links</em> code in the proper sense of the word: linking up module entry points as callbacks.</p>

<p>Basically, snippets can now have inputs and outputs that are themselves functions. These connections don't obey the typical data flow of a directed graph and instead are for function calls. A callback connection provides a path along which calls are made and values are returned.</p>

</div></div>

<div class="c"></div>

<div class="wide full mt1">

  <div class="iframe c">
    <iframe src="/files/mathbox2/iframe-shadergraph-2.html?c3d6624d" class="janky" height="300" style="width: 100%;"></iframe>
  </div>

</div>

<div class="c"></div>

<div class="g8 i2"><div class="pad">

<p class="mt0">Snippets can be instanced multiple times, including their uniforms, attributes and varyings (if requested). Uniforms are bound to Three.js-style registers as you build the graph incrementally. So it's a module system, sort of, which enables functional shader building. Using callbacks as micro-interfaces feels very natural in practice, especially with bound parameters. You can decorate existing functions, e.g. turning a texture sampler into a convolution filter.</p>

</div></div>

<div class="c"></div>

<div class="wide full">

  <div class="iframe c">
    <iframe src="/files/mathbox2/iframe-shadergraph-7.html?c3d6624d" class="janky" height="300" style="width: 100%;"></iframe>
  </div>

</div>

<div class="c"></div>

<div class="g8 i2 mt0"><div class="pad">

<pre><code class="language-javascript">// Build shader graph
var shader = shadergraph.shader();
shader
  .callback()
    .pipe('sampleColor')
    .fan()
      .pipe('sepiaColor')
    .next()
      .pipe('invertColor')
    .join()
    .pipe('combineColors')
  .join()
  .pipe('convolveColor');</code></pre>

<div class="c"></div>

<h2>GLSL Composer</h2>

<p>If you know GLSL, you can write ShaderGraph snippets: there is no extra syntax, you just add inputs and outputs to your <code>main()</code> function. You can use <code>in/out/inout</code> qualifiers or return a value. If there's no main function, the last defined function is exported.</p>

<pre><code class="language-glsl">vec3 callback(vec3 arg1, vec3 arg2);</code></pre>

<div class="c"></div>

<p>To create a <em>callback input</em> in a snippet, you declare a function prototype in GLSL without a body. The function name and signature is used to create the outlet.</p>

<p>To create a <em>callback output</em>, you use the factory API. You can <code>.require()</code> a snippet directly, or bundle up a subgraph with <code>.callback().….join()</code>. In the latter case, the function signature includes all unconnected inputs and outputs inside. Outlets are auto-matched by name, type and order, with the semantics from v1 cleaned up.</p>

<p>Building basic pipes is easy: <code>.pipe(…).pipe(…).…</code>, passing in a snippet or factory. For forked graphs, you can <code>.fan()</code> (1-to-N) or <code>.split()</code> (N-to-N), use <code>.next()</code> to begin a new branch, and then <code>.join()</code> at the end. There's a few other operations, nothing crazy.</p>

<pre><code class="language-javascript">var v = shadergraph.shader();

// Graphs generated elsewhere
v.pipe(vertexColor(color, mask));
v.require(vertexPosition(position, material, map, 2, stpq));

v.pipe('line.position',    uniforms, defs);
v.pipe('project.position', uniforms);
</code></pre>  

<div class="c"></div>

<p>By connecting pairs you create a functional data flow that compiles down to vanilla GLSL. It's not functional programming <em>in</em> GLSL, it just enables useful run-time assembly patterns, letting the snippets do the heavy lifting the old fashioned way.</p>

</div></div>

<div class="c"></div>

<div class="wide full mt2">

  <div class="iframe c">
    <iframe src="/files/mathbox2/iframe-shadergraph-5.html?c3d6624d" class="mathbox janky" height="440" style="width: 100%;"></iframe>
  </div>

  <div class="iframe c">
    <iframe src="/files/mathbox2/iframe-shadergraph-4.html?c3d6624d" class="janky" height="440" style="width: 100%;"></iframe>
  </div>

</div>

<div class="c"></div>

<div class="g8 i2"><div class="pad">

<p>As GPUs are massively parallel pure function applicators, the resulting mega-shaders are a great fit.</p>

</div></div>

<div class="wide full">

  <div class="iframe c">
    <iframe src="/files/mathbox2/iframe-shadergraph-3.html?c3d6624d" class="mathbox janky" height="725" style="width: 100%;"></iframe>
  </div>

</div>

<div class="c"></div>

<div class="g8 i2"><div class="pad">

<h2><code>$ cat *.glsl | magic</code></h2>

<p>The process still comes down to concatenating the code in a clever way, with global symbols namespaced to be unique. Function bodies are generated to call snippets in the right order, and the callbacks are linked. In the trivial case it links a callback by <code>#define</code>ing the two symbols to be the same. It can also impedance match compatible signatures like <code>void main(in float, out vec2)</code> and <code>vec2 main(float)</code> by inserting an intermediate call.</p>

</div></div>

<div class="g10 i1"><div class="pad">
  
  <pre><code class="language-glsl small" style="max-height: 480px; overflow-y: auto;">precision highp float;
precision highp int;
uniform mat4 modelMatrix;
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
uniform mat4 viewMatrix;
uniform mat3 normalMatrix;
uniform vec3 cameraPosition;
#define _sn_191_getPosition _pg_103_
#define _sn_190_getPosition _pg_102_
#define _sn_189_getSample _pg_100_
#define _pg_99_ _sn_185_warpVertex
#define _pg_103_ _sn_190_getMeshPosition
#define _pg_100_ _sn_188_getTransitionSDFMask
#define _pg_101_ _sn_189_maskLevel
vec2 _sn_180_truncateVec(vec4 v) { return v.xy; }
uniform vec2 _sn_181_dataResolution;
uniform vec2 _sn_181_dataPointer;

vec2 _sn_181_map2DData(vec2 xy) {
  return fract((xy + _sn_181_dataPointer) * _sn_181_dataResolution);
}

uniform sampler2D _sn_182_dataTexture;

vec4 _sn_182_sample2D(vec2 uv) {
  return texture2D(_sn_182_dataTexture, uv);
}

vec4 _sn_183_swizzle(vec4 xyzw) {
  return vec4(xyzw.x, xyzw.w, 0.0, 0.0);
}
uniform float _sn_184_polarBend;
uniform float _sn_184_polarFocus;
uniform float _sn_184_polarAspect;
uniform float _sn_184_polarHelix;

uniform mat4 _sn_184_viewMatrix;

vec4 _sn_184_getPolarPosition(vec4 position, inout vec4 stpq) {
  if (_sn_184_polarBend &gt; 0.0) {

    if (_sn_184_polarBend &lt; 0.001) {
      
      
      
      
      vec2 pb = position.xy * _sn_184_polarBend;
      float ppbbx = pb.x * pb.x;
      return _sn_184_viewMatrix * vec4(
        position.x * (1.0 - _sn_184_polarBend + (pb.y * _sn_184_polarAspect)),
        position.y * (1.0 - .5 * ppbbx) - (.5 * ppbbx) * _sn_184_polarFocus / _sn_184_polarAspect,
        position.z + position.x * _sn_184_polarHelix * _sn_184_polarBend,
        1.0
      );
    }
    else {
      vec2 xy = position.xy * vec2(_sn_184_polarBend, _sn_184_polarAspect);
      float radius = _sn_184_polarFocus + xy.y;
      return _sn_184_viewMatrix * vec4(
        sin(xy.x) * radius,
        (cos(xy.x) * radius - _sn_184_polarFocus) / _sn_184_polarAspect,
        position.z + position.x * _sn_184_polarHelix * _sn_184_polarBend,
        1.0
      );
    }
  }
  else {
    return _sn_184_viewMatrix * vec4(position.xyz, 1.0);
  }
}
uniform float _sn_185_time;
uniform float _sn_185_intensity;

vec4 _sn_185_warpVertex(vec4 xyzw, inout vec4 stpq) {
  xyzw +=   0.2 * _sn_185_intensity * (sin(xyzw.yzwx * 1.91 + _sn_185_time + sin(xyzw.wxyz * 1.74 + _sn_185_time)));
  xyzw +=   0.1 * _sn_185_intensity * (sin(xyzw.yzwx * 4.03 + _sn_185_time + sin(xyzw.wxyz * 2.74 + _sn_185_time)));
  xyzw +=  0.05 * _sn_185_intensity * (sin(xyzw.yzwx * 8.39 + _sn_185_time + sin(xyzw.wxyz * 4.18 + _sn_185_time)));
  xyzw += 0.025 * _sn_185_intensity * (sin(xyzw.yzwx * 15.1 + _sn_185_time + sin(xyzw.wxyz * 9.18 + _sn_185_time)));

  return xyzw;
}



vec4 _sn_186_getViewPosition(vec4 position, inout vec4 stpq) {
  return (viewMatrix * vec4(position.xyz, 1.0));
}

vec3 _sn_187_getRootPosition(vec4 position, in vec4 stpq) {
  return position.xyz;
}
vec3 _pg_102_(vec4 _io_510_v, in vec4 _io_519_stpq) {
  vec2 _io_509_return;
  vec2 _io_511_return;
  vec4 _io_513_return;
  vec4 _io_515_return;
  vec4 _io_517_return;
  vec4 _io_520_stpq;
  vec4 _io_527_return;
  vec4 _io_528_stpq;
  vec4 _io_529_return;
  vec4 _io_532_stpq;
  vec3 _io_533_return;

  _io_509_return = _sn_180_truncateVec(_io_510_v);
  _io_511_return = _sn_181_map2DData(_io_509_return);
  _io_513_return = _sn_182_sample2D(_io_511_return);
  _io_515_return = _sn_183_swizzle(_io_513_return);
  _io_520_stpq = _io_519_stpq;
  _io_517_return = _sn_184_getPolarPosition(_io_515_return, _io_520_stpq);
  _io_528_stpq = _io_520_stpq;
  _io_527_return = _pg_99_(_io_517_return, _io_528_stpq);
  _io_532_stpq = _io_528_stpq;
  _io_529_return = _sn_186_getViewPosition(_io_527_return, _io_532_stpq);
  _io_533_return = _sn_187_getRootPosition(_io_529_return, _io_532_stpq);
  return _io_533_return;
}
uniform vec4 _sn_190_geometryResolution;

#ifdef POSITION_STPQ
varying vec4 vSTPQ;
#endif
#ifdef POSITION_U
varying float vU;
#endif
#ifdef POSITION_UV
varying vec2 vUV;
#endif
#ifdef POSITION_UVW
varying vec3 vUVW;
#endif
#ifdef POSITION_UVWO
varying vec4 vUVWO;
#endif


vec3 _sn_190_getMeshPosition(vec4 xyzw, float canonical) {
  vec4 stpq = xyzw * _sn_190_geometryResolution;
  vec3 xyz = _sn_190_getPosition(xyzw, stpq);

  #ifdef POSITION_MAP
  if (canonical &gt; 0.5) {
    #ifdef POSITION_STPQ
    vSTPQ = stpq;
    #endif
    #ifdef POSITION_U
    vU = stpq.x;
    #endif
    #ifdef POSITION_UV
    vUV = stpq.xy;
    #endif
    #ifdef POSITION_UVW
    vUVW = stpq.xyz;
    #endif
    #ifdef POSITION_UVWO
    vUVWO = stpq;
    #endif
  }
  #endif
  return xyz;
}

uniform float _sn_188_transitionEnter;
uniform float _sn_188_transitionExit;
uniform vec4  _sn_188_transitionScale;
uniform vec4  _sn_188_transitionBias;
uniform float _sn_188_transitionSkew;
uniform float _sn_188_transitionActive;

float _sn_188_getTransitionSDFMask(vec4 stpq) {
  if (_sn_188_transitionActive &lt; 0.5) return 1.0;

  float enter   = _sn_188_transitionEnter;
  float exit    = _sn_188_transitionExit;
  float skew    = _sn_188_transitionSkew;
  vec4  scale   = _sn_188_transitionScale;
  vec4  bias    = _sn_188_transitionBias;

  float factor  = 1.0 + skew;
  float offset  = dot(vec4(1.0), stpq * scale + bias);

  vec2 d = vec2(enter, exit) * factor + vec2(-offset, offset - skew);
  if (exit  == 1.0) return d.x;
  if (enter == 1.0) return d.y;
  return min(d.x, d.y);
}
uniform float _sn_191_worldUnit;
uniform float _sn_191_lineWidth;
uniform float _sn_191_lineDepth;
uniform float _sn_191_focusDepth;

uniform vec4 _sn_191_geometryClip;
attribute vec2 line;
attribute vec4 position4;

#ifdef LINE_PROXIMITY
uniform float _sn_191_lineProximity;
varying float vClipProximity;
#endif

#ifdef LINE_STROKE
varying float vClipStrokeWidth;
varying float vClipStrokeIndex;
varying vec3  vClipStrokeEven;
varying vec3  vClipStrokeOdd;
varying vec3  vClipStrokePosition;
#endif


#ifdef LINE_CLIP
uniform float _sn_191_clipRange;
uniform vec2  _sn_191_clipStyle;
uniform float _sn_191_clipSpace;

attribute vec2 strip;

varying vec2 vClipEnds;

void _sn_191_clipEnds(vec4 xyzw, vec3 center, vec3 pos) {

  
  vec4 xyzwE = vec4(strip.y, xyzw.yzw);
  vec3 end   = _sn_191_getPosition(xyzwE, 0.0);

  
  vec4 xyzwS = vec4(strip.x, xyzw.yzw);
  vec3 start = _sn_191_getPosition(xyzwS, 0.0);

  
  vec3 diff = end - start;
  float l = length(diff) * _sn_191_clipSpace;

  
  float arrowSize = 1.25 * _sn_191_clipRange * _sn_191_lineWidth * _sn_191_worldUnit;

  vClipEnds = vec2(1.0);

  if (_sn_191_clipStyle.y &gt; 0.0) {
    
    float depth = _sn_191_focusDepth;
    if (_sn_191_lineDepth &lt; 1.0) {
      float z = max(0.00001, -end.z);
      depth = mix(z, _sn_191_focusDepth, _sn_191_lineDepth);
    }
    
    
    float size = arrowSize * depth;

    
    
    float mini = clamp(1.0 - l / size * .333, 0.0, 1.0);
    float scale = 1.0 - mini * mini * mini; 
    float invrange = 1.0 / (size * scale);
  
    
    diff = normalize(end - center);
    float d = dot(end - pos, diff);
    vClipEnds.x = d * invrange - 1.0;
  }

  if (_sn_191_clipStyle.x &gt; 0.0) {
    
    float depth = _sn_191_focusDepth;
    if (_sn_191_lineDepth &lt; 1.0) {
      float z = max(0.00001, -start.z);
      depth = mix(z, _sn_191_focusDepth, _sn_191_lineDepth);
    }
    
    
    float size = arrowSize * depth;

    
    
    float mini = clamp(1.0 - l / size * .333, 0.0, 1.0);
    float scale = 1.0 - mini * mini * mini; 
    float invrange = 1.0 / (size * scale);
  
    
    diff = normalize(center - start);
    float d = dot(pos - start, diff);
    vClipEnds.y = d * invrange - 1.0;
  }


}
#endif

const float _sn_191_epsilon = 1e-5;
void _sn_191_fixCenter(vec3 left, inout vec3 center, vec3 right) {
  if (center.z &gt;= 0.0) {
    if (left.z &lt; 0.0) {
      float d = (center.z - _sn_191_epsilon) / (center.z - left.z);
      center = mix(center, left, d);
    }
    else if (right.z &lt; 0.0) {
      float d = (center.z - _sn_191_epsilon) / (center.z - right.z);
      center = mix(center, right, d);
    }
  }
}


void _sn_191_getLineGeometry(vec4 xyzw, float edge, out vec3 left, out vec3 center, out vec3 right) {
  vec4 delta = vec4(1.0, 0.0, 0.0, 0.0);

  center =                 _sn_191_getPosition(xyzw, 1.0);
  left   = (edge &gt; -0.5) ? _sn_191_getPosition(xyzw - delta, 0.0) : center;
  right  = (edge &lt; 0.5)  ? _sn_191_getPosition(xyzw + delta, 0.0) : center;
}

vec3 _sn_191_getLineJoin(float edge, bool odd, vec3 left, vec3 center, vec3 right, float width) {
  vec2 join = vec2(1.0, 0.0);

  _sn_191_fixCenter(left, center, right);

  vec4 a = vec4(left.xy, right.xy);
  vec4 b = a / vec4(left.zz, right.zz);

  vec2 l = b.xy;
  vec2 r = b.zw;
  vec2 c = center.xy / center.z;

  vec4 d = vec4(l, c) - vec4(c, r);
  float l1 = dot(d.xy, d.xy);
  float l2 = dot(d.zw, d.zw);

  if (l1 + l2 &gt; 0.0) {
    
    if (edge &gt; 0.5 || l2 == 0.0) {
      vec2 nl = normalize(d.xy);
      vec2 tl = vec2(nl.y, -nl.x);

#ifdef LINE_PROXIMITY
      vClipProximity = 1.0;
#endif

#ifdef LINE_STROKE
      vClipStrokeEven = vClipStrokeOdd = normalize(left - center);
#endif
      join = tl;
    }
    else if (edge &lt; -0.5 || l1 == 0.0) {
      vec2 nr = normalize(d.zw);
      vec2 tr = vec2(nr.y, -nr.x);

#ifdef LINE_PROXIMITY
      vClipProximity = 1.0;
#endif

#ifdef LINE_STROKE
      vClipStrokeEven = vClipStrokeOdd = normalize(center - right);
#endif
      join = tr;
    }
    else {
      
      float lmin2 = min(l1, l2) / (width * width);

      
#ifdef LINE_PROXIMITY
      float lr     = l1 / l2;
      float rl     = l2 / l1;
      float ratio  = max(lr, rl);
      float thresh = _sn_191_lineProximity + 1.0;
      vClipProximity = (ratio &gt; thresh * thresh) ? 1.0 : 0.0;
#endif
      
      
      vec2 nl = normalize(d.xy);
      vec2 nr = normalize(d.zw);

      vec2 tl = vec2(nl.y, -nl.x);
      vec2 tr = vec2(nr.y, -nr.x);

#ifdef LINE_PROXIMITY
      
      vec2 tc = normalize(mix(tl, tr, l1/(l1+l2)));
#else
      
      vec2 tc = normalize(tl + tr);
#endif
    
      float cosA   = dot(nl, tc);
      float sinA   = max(0.1, abs(dot(tl, tc)));
      float factor = cosA / sinA;
      float scale  = sqrt(1.0 + min(lmin2, factor * factor));

#ifdef LINE_STROKE
      vec3 stroke1 = normalize(left - center);
      vec3 stroke2 = normalize(center - right);

      if (odd) {
        vClipStrokeEven = stroke1;
        vClipStrokeOdd  = stroke2;
      }
      else {
        vClipStrokeEven = stroke2;
        vClipStrokeOdd  = stroke1;
      }
#endif
      join = tc * scale;
    }
    return vec3(join, 0.0);
  }
  else {
    return vec3(0.0);
  }

}

vec3 _sn_191_getLinePosition() {
  vec3 left, center, right, join;

  float edge = line.x;
  float offset = line.y;

  vec4 p = min(_sn_191_geometryClip, position4);
  edge += max(0.0, position4.x - _sn_191_geometryClip.x);

  
  _sn_191_getLineGeometry(p, edge, left, center, right);

#ifdef LINE_STROKE
  
  vClipStrokePosition = center;
  vClipStrokeIndex = p.x;
  bool odd = mod(p.x, 2.0) &gt;= 1.0;
#else
  bool odd = true;
#endif

  
  float width = _sn_191_lineWidth * 0.5;

  float depth = _sn_191_focusDepth;
  if (_sn_191_lineDepth &lt; 1.0) {
    
    float z = max(0.00001, -center.z);
    depth = mix(z, _sn_191_focusDepth, _sn_191_lineDepth);
  }
  width *= depth;

  
  width *= _sn_191_worldUnit;

  join = _sn_191_getLineJoin(edge, odd, left, center, right, width);

#ifdef LINE_STROKE
  vClipStrokeWidth = width;
#endif
  
  vec3 pos = center + join * offset * width;

#ifdef LINE_CLIP
  _sn_191_clipEnds(p, center, pos);
#endif

  return pos;
}

uniform vec4 _sn_189_geometryResolution;
uniform vec4 _sn_189_geometryClip;
varying float vMask;


void _sn_189_maskLevel() {
  vec4 p = min(_sn_189_geometryClip, position4);
  vMask = _sn_189_getSample(p * _sn_189_geometryResolution);
}

uniform float _sn_192_styleZBias;
uniform float _sn_192_styleZIndex;

void _sn_192_setPosition(vec3 position) {
  vec4 pos = projectionMatrix * vec4(position, 1.0);

  
  float bias  = (1.0 - _sn_192_styleZBias / 32768.0);
  pos.z *= bias;
  
  
  if (_sn_192_styleZIndex &gt; 0.0) {
    float z = pos.z / pos.w;
    pos.z = ((z + 1.0) / (_sn_192_styleZIndex + 1.0) - 1.0) * pos.w;
  }
  
  gl_Position = pos;
}
void main() {
  vec3 _io_546_return;

  _io_546_return = _sn_191_getLinePosition();
  _sn_192_setPosition(_io_546_return);
  _pg_101_();
}
</code></pre>

</div></div>

<div class="g8 i2"><div class="pad">

<p>It still does guarded regex manipulation of code too, but those manipulations are now derived from a proper syntax tree. GLSL doesn't have strings and its scope is simple, so this is unusually safe. I'm sure you can still trip it up somehow, but it's worth it for speed. I'm seeing assembly times of ~10-30ms cold, 2-4ms warm, but it depends entirely on the particular shaders.</p>

<p>The assembly process is now properly recursive. Unassembled shaders can be used in factory form, standing in for snippets. Completed graphs form stand-alone programs with no open inputs or outputs. The result can be turned straight into a Three.js <code>ShaderMaterial</code>, but there is no strict Three dependency. It's just a dictionary with code and a list of uniforms, attributes and varyings. Unlike before, building a combined vertex/fragment program is now merely syntactic sugar for a pair of separate graphs.</p>

<p>As it's run-time, you can slot in user-defined or code-generated GLSL just the same. Shaders are fetched by name or passed as inline code, mixed freely as needed. You supply the dictionary or lookup method. You could bundle your GLSL into JS with a build step or include embedded <code>&lt;script&gt;</code> tags.</p>

</div></div>

<div class="c"></div>

<div class="wide full">

  <div class="iframe c">
    <iframe src="/files/mathbox2/iframe-shadergraph-6.html?c3d6624d" class="mathbox janky" height="700" style="width: 100%;"></iframe>
  </div>

</div>

<div class="c"></div>

<div class="g8 i2"><div class="pad">

  <p><em>This is the fragment shader that implements the partial differential equation for this ripple effect (<code>getFramesSample</code>). It samples from a volumetric N×N×2 array, feeding back into itself.</em></p>
  
<h2>Paging Dr. Hickey</h2>

<p>ShaderGraph 2 drives the entirety of MathBox 2. Its shaders are specialized for particular types and dimensions, generating procedural data, clipping geometry, resampling transformed data on the fly, …. The composibility comes out naturally. To do so, I pass a partially built <code>factory</code> by interested parties. This way I build graphs for position, color, normal, mask and more. These are injected as callbacks into a final shader. Shader factories enable ad-hoc contracts, sandwiched between the inner and outer retained layers of Three.js and MathBox, but disappearing entirely in the end result.</p>

<p>Of course, all of this is meta-programming of GLSL, done through a stateful JS lasagna and a ghetto compiler, instead of an idiomatic language. I know this, it's an inner platform effect bathing luxuriously in Turing tar like a rhino in mud. I didn't really see a way around it, given the constraints at play.</p>

<p>While the factory API is designed for making graphs on the spot and then tossing them, you could keep graphs around. There's a full data model underneath. You can always skip the factory entirely.</p>

<p>Plenty of caveats of course. There is no built-in preprocessor, so you can't <code>#define</code> or <code>#ifdef</code> uniforms or attributes and have it make sense. But then the point of ShaderGraph is to formalize exactly that sort of ad-hoc fiddling. Preprocessor directives will just pass through. <code>glsl-parser</code> has gaps too, and it is also exceedingly picky with reserved variable names, so watch out for that.</p>

<p>I did sometimes feel the need for more powerful metaprogramming, but you can work around it. It is easy to dynamically make GLSL one-liner snippets and feed them in. String manipulation of code is always still an option, you just don't need to do it at the macro-level anymore.</p>

<p>ShaderGraph 2 has been in active use now for months, it does the job I need it to very well. In a perfect world, this would be solved at the GPU driver level. Until SPIR-V or WebVulkan gets here, imma stick to my regexes. Don't try this at home, kids.</p>

<p class="mt1"><em>For docs and more, see the <a href="https://github.com/unconed/shadergraph">Git repository</a>.</em></p>

<ul class="mt3">
  <li><a href="/blog/mathbox2/">MathBox² - PowerPoint Must Die</a></li>
  <li><a href="/blog/a-dom-for-robots/">A DOM for Robots - Modelling Live Data</a></li>
  <li><a href="/blog/yak-shading/">Yak Shading - Data Driven Geometry</a></li>
  <li><em>ShaderGraph 2 - Functional GLSL</em></li>
</ul>

</div></div>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[MathBox²]]></title>
    <link href="https://acko.net/blog/mathbox2/"/>
    <updated>2015-09-27T00:00:00+02:00</updated>
    <id>https://acko.net/blog/mathbox2</id>
    <content type="html"><![CDATA[<script src="/files/katex/katex.min.js"></script>

<script src="/files/katex/contrib/auto-render.min.js"></script>

<link rel="stylesheet" type="text/css" href="/files/katex/katex.min.css" />

<script type="text/javascript">
Acko.queue(function () {
  renderMathInElement(document.querySelector('article'), {delimiters: [
    {left: "$$", right: "$$", display: true},
    {left: "($", right: "$)", display: false},
  ]});
});
</script>

<div class="g8 i2 first"><div class="pad">

<h2 class="sub">PowerPoint Must Die</h2>

</div></div>

<div class="c"></div>

<div class="g12"><div class="pad">

<blockquote class="m2">
  <em class="big">"<strong>I think a lot of mathematics is really about how you understand things in your head.</strong> It's people that did mathematics, we're not just general purpose machines, we're people. We see things, we feel things, we think of things. A lot of what I have done in my mathematical career has had to do with finding new ways to build models, to see things, do computations. Really get a feel for stuff.<br /><br />
It may seem unimportant, but when I started out people drew pictures of 3-manifolds one way and I started drawing them a different way. People drew pictures of surfaces one way and I started drawing them a different way. <strong>There's something significant about how the representation in your head profoundly changes how you think.</strong><br /><br />
It's very hard to do a brain dump. Very hard to do that. But I'm still going to try to do something to give a feel for 3-manifolds. Words are one thing, we can talk about geometric structures. There are many precise mathematical words that could be used, but they don't automatically convey a feeling for it. <strong>I probably can't convey a feeling for it either, but I want to try.</strong>"</em>
<div class="tr m1">– <a href="https://duckduckgo.com/?q=William+Thurston" target="_blank">William Thurston</a>, The Mystery of 3-Manifolds (<a href="https://www.youtube.com/watch?v=4jdmkUQDWtQ&amp;t=168s" target="_blank">Video</a>)</div>
</blockquote>

</div></div>

<div class="g8 i2 m2"><div class="pad">

<p>How do you convince web developers—heck, people in general—to care about math? This was the challenge underlying <a href="/tv/wdcode/">Making Things With Maths</a>, a talk I gave three years ago. I didn't know either, I just knew why I liked this stuff: demoscene, games, simulation, physics, VR, … It had little to do with what passed for mathematics in my own engineering education. There we were served only eyesore PowerPoints or handwritten overhead transparencies, with simplified graphs, abstract flowcharts and rote formulas, available on black and white photocopies.</p>

<p>Smart people who were supposed to teach us about technology seemed unable to teach us <em>with</em> technology. Fixing this felt like a huge challenge where I'd have to start from scratch. This is why the focus was entirely on showing rather than telling, and why MathBox 1 was born. It's how this stuff looks and feels in my head, and how I got my degree: by translating formulas into mental pictures, which I could replay and reason about on demand.</p>

<h2>PowerPoint Syndrome</h2>

<p>Initially I used MathBox like an embedded image or video: compact diagrams, each a point or two in a presentation. My style quickly shifted though. I kept on finding ways to transform from one visualization to another. Not for show, but to reveal the similarities and relationships underneath. MathBox encouraged me to animate things correctly, leveraging the actual models themselves, instead of doing a visual morph from A to B. Each animation became a continuous stream of valid examples, a quality both captivating and revealing.</p>

</div></div>

<div class="c"></div>

<div class="wide mt2">
    <a target="_blank" href="/blog/how-to-fold-a-julia-fractal/"><img src="/files/mathbox2/julia-fractal.jpg" alt="How to Fold a Julia Fractal" /></a>
</div>

<div class="g8 i2 m2"><div class="pad">

<p>For instance, <a href="/blog/how-to-fold-a-julia-fractal/">How to Fold a Julia Fractal</a> is filled with animations of complex exponentials, right from the get go. This way I avoid the scare that ($ e^{i\pi} $) is a meaningful expression; symbology and tau-tology never have a chance to obscure geometrical workings. Instead a web page that casually demonstrates conformal mapping and complex differential equations got 340,000 visits. Despite spotty web browser support and excluding all mobile phones for years.</p>

</div></div>

<aside class="g4 m3 ti tc muted"><div class="pad">
  <img src="/files/mathbox2/elsevier.png" style="max-width: 75%; margin: 0 auto;" alt="Elsevier $42 per PDF paywall" />
  <p>Meanwhile academics voluntarily published their writings behind a&nbsp;<a href="http://www.elsevier.com" rel="nofollow">$42&nbsp;per&nbsp;PDF</a> paywall, the&nbsp;<em>colossal&nbsp;idiots</em>.</p>
</div></aside>

<div class="g7 m1"><div class="pad">

<p>The next talk, <a href="/tv/webglmath/">Making WebGL Dance</a>, contained elaborate <em>long takes</em> worthy of an Alfonso Cuarón film, with only 3 separate shots for the bulk of a 30 minute talk. The lesson seemed obvious: the slides shouldn't have graphics in them, rather, <em>the graphics should have slides in them</em>. The diagnosis of PowerPoint syndrome is then the constant trashing of context from one slide to the next. A traditional blackboard doesn't have this problem: you build up diagrams slowly, by hand, across a large surface, erasing selectively and only when you run out of space.</p>

</div></div>

<div class="c"></div>

<div class="g8 i2 m2"><div class="pad">

<p>It's not just about permanence and progression though, it's also about leveraging our natural understanding of shape, scale, color and motion. Think of how a toddler learns to interact with the world: poke, grab, chew, spit, smash. Which evolves into run, jump, fall, get back up again. Humans are naturals at taking multiple cases of "If I do this, that will happen" and turning it into a consistent, functional model of how things work. We learn language by bootstrapping random jibberish into situational meaning, converging on a shared protocol.</p>

<p>That said, I find the usual descriptions of how people experience language and thought foreign. Instead, when <a href="http://grandin.com" target="_blank">Temple Grandin</a> speaks about visual thinking, I nod vigorously. Thought to me is analog concepts and sensory memories, remixed with visual and other simulations. It builds off the quantities and qualities present in spatial and temporal notions, which appear built-in to us.</p>

</div></div>

<div class="g8 m1"><div class="pad">

<p>Speech and writing is then a program designed to reconstruct particular thoughts in a compatible brain. There are a multitude of evolving languages, they can be used elegantly, bluntly, incomprehensibly, but the desired output remains the same. In my talks, armed with weapons-grade <a href="https://duckduckgo.com/?q=c2+continuous" target="_blank">C2-continuous</a> animations, it is easy to transcode my film reel into words, because the slides run themselves. The string of concepts already hangs in the air, I only add the missing grammar that links them up. This is a puzzle our brains are so good at solving, we usually do it <em>without</em> thinking.</p>

<p>Language is the ability of thoughts to compute their own source code.</p>

<p>(It's not proof, I just supply pudding.)</p>

</div></div>

<aside class="g4 m2"><div class="pad">
  <img src="/files/mathbox2/remote.jpg" alt="powerpoint remote" style="max-width: 50%; margin: 0 auto" />
  <p class="tc">Tip: Powerpoint remotes are 4-key USB keyboards with <kbd>PageUp</kbd>/<kbd>PageDown</kbd>, <kbd>F5</kbd> and <kbd>.</kbd> keys.<br /><br />Comes with dongle.</p>
</div></aside>

<div class="c"></div>

<div class="g6 m3"><div class="pad">
  <img src="/files/mathbox2/sketch1.jpg" alt="mathbox presentation slide sketches" />
</div></div>
<div class="g6 m2"><div class="pad">
  <img src="/files/mathbox2/sketch2.jpg" alt="mathbox presentation slide sketches" />
</div></div>

<aside class="g12"><div class="pad">
  <p class="tc">I sketch rough thumbnails, then start animating until I hit a dead end. Then start another one. Titles and overlays always come last.</p>
</div></aside>

<div class="g8 i2 m2"><div class="pad">

<h2>Manifold Dreams</h2>

<p>I don't say all this to up my Rain Man cred, but to lay to rest the recurring question of where my work comes from. I translate the pictures in my head to HD, in order to learn from and refine the view. As I did with <a href="/blog/animate-your-way-to-glory-pt2/#quaternions">quaternions</a>: I struggled to grok the hypersphere, it wouldn't fit together right. So I wrote the code to trace out geodesics in color and fly around in it, and suddenly the twisting made sense. Hence my entire tutorial was built to replicate the same discovery process I went through myself.</p>

</div></div>

<div class="c"></div>

<aside class="wide full">

  <div class="iframe c m1">
    <iframe src="/files/mathbox2/iframe-quat.html?c3d6624d" class="mathbox autosize janky" height="320"></iframe>

    <p class="tc">For visualizing the 4D hypersphere, quaternions are a natural fit.<br />They reveal their underlying cyclic symmetry under 4D stereographic projection.</p>
  </div>

</aside>

<div class="g8 i2"><div class="pad">

<p>There was one big problem: scenes now consisted of <em>diagrams of diagrams</em>, which meant working around MathBox more than with it. Performance issues arose as complexity grew. Above all there was a total lack of composability in the components. None of this could be fixed without ripping out significant pieces, so doing it incrementally seemed futile. I started from scratch and set off to reinvent all the wheels.</p>

<p class="tc">$$ \text{MathBox}^2 = \int_1^2 \text{code}(v) dv $$</p>

<p>MathBox 2 was inevitably going to suffer <em>second-system syndrome</em>, parts would be overengineered. Rather than fight it, I embraced it and effectively wrote a strange vector GPU driver in CoffeeScript. (Such is life, this is a blueprint meant to be simplified and made obsolete over time, not expanded upon.) It's a freight train straight to the heart of a graphics card, combining low-level and high-level in a way that feels novel <span style="font-size:0;opacity:0;width:0;position:absolute;left:-10000px;">🐴</span> when you use it, squeezing <span style="font-size:0;opacity:0;width:0;position:absolute;left:-10000px;">🐴</span> through a very small opening.</p>

<p>What was tedious before, now falls out naturally. If I format the scene above as XML/JSX, it becomes:</p>

</div></div>

<div class="g8 i2"><div class="pad">
  <!--first-->

<pre><code class="language-jsx wrap small"><span style="color:rgb(128,0,128)">&lt;root&gt;
  <span style="color:rgb(0,70,156)">&lt;!-- Place the camera --&gt;</span>
  &lt;camera /&gt;
  <span style="color:rgb(0,70,156)">&lt;!-- Change clock speed --&gt;</span>
  &lt;clock&gt;
    <span style="color:rgb(0,70,156)">&lt;!-- 4D Stereographic projection --&gt;</span>
    &lt;stereographic4&gt;
      <span style="color:rgb(0,70,156)">&lt;!-- Custom 4D rotation shader --&gt;</span>
      &lt;shader /&gt;
      <span style="color:rgb(0,70,156)">&lt;!-- Move vertices --&gt;</span>
      &lt;vertex&gt;
        <span style="color:rgb(0,70,156)">&lt;!-- Sample an area --&gt;</span>
        <span style="color:rgb(0,70,156)">&lt;!-- Draw a set of lines --&gt;</span>
        <span style="color:rgb(32,128,240)">&lt;area /&gt;</span>
        <span style="color:rgb(32,128,240)">&lt;line /&gt;</span>

        <span style="color:rgb(0,70,156)">&lt;!-- Sample an area --&gt;</span>
        <span style="color:rgb(0,70,156)">&lt;!-- Draw a set of lines --&gt;</span>
        <span style="color:rgb(32,140,0)">&lt;area /&gt;</span>
        <span style="color:rgb(32,140,0)">&lt;line /&gt;</span>

        <span style="color:rgb(0,70,156)">&lt;!-- Sample an area --&gt;</span>
        <span style="color:rgb(0,70,156)">&lt;!-- Draw a set of lines --&gt;</span>
        <span style="color:rgb(203,32,0)">&lt;area /&gt;</span>
        <span style="color:rgb(203,32,0)">&lt;line /&gt;</span>
      &lt;/vertex&gt;
    &lt;/stereographic4&gt;
  &lt;/clock&gt;
&lt;/root&gt;</span></code></pre>

</div></div>

<div class="c"></div>

<div class="g12 m1"><div class="pad">
  
<p>In order to make these pieces behave, a bunch of additional attributes are applied, most of which are strings or values, some of which are functions/code, either JavaScript or GLSL:</p>

</div></div>

<div class="g12"><div class="pad">
  <!--second-->

<pre><code class="language-jsx wrap small"><span></span><span style="color:rgb(128,0,128)">&lt;root </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">1</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">scale</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{300}</span><span style="color:rgb(128,0,128)"></span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;camera </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">2</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">proxy</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{true}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">position</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{[0, 0, 3]}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;clock </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">3</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">speed</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{1/4}</span><span style="color:rgb(128,0,128)"></span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;stereographic4 </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">4</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">bend</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{1}</span><span style="color:rgb(128,0,128)"></span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;shader </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">5</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">code</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">
uniform float cos1;
uniform float sin1;
uniform float cos2;
uniform float sin2;
uniform float cos3;
uniform float sin3;
uniform float cos4;
uniform float sin4;

vec4 getRotate4D(vec4 xyzw, inout vec4 stpq) {
  xyzw.xy = xyzw.xy * mat2(cos1, sin1, -sin1, cos1);
  xyzw.zw = xyzw.zw * mat2(cos2, sin2, -sin2, cos2);
  xyzw.xz = xyzw.xz * mat2(cos3, sin3, -sin3, cos3);
  xyzw.yw = xyzw.yw * mat2(cos4, sin4, -sin4, cos4);

  return xyzw;
}</span><span style="color:rgb(128,0,128)">&quot;
 </span><span style="color:rgb(144,64,0)">cos1</span><span style="color:rgb(128,0,128)">=&gt;</span><span style="color:rgb(0,70,156)">{(t) =&gt; Math.cos(t * .111)}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">sin1</span><span style="color:rgb(128,0,128)">=&gt;</span><span style="color:rgb(0,70,156)">{(t) =&gt; Math.sin(t * .111)}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">cos2</span><span style="color:rgb(128,0,128)">=&gt;</span><span style="color:rgb(0,70,156)">{(t) =&gt; Math.cos(t * .151 + 1)}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">sin2</span><span style="color:rgb(128,0,128)">=&gt;</span><span style="color:rgb(0,70,156)">{(t) =&gt; Math.sin(t * .151 + 1)}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">cos3</span><span style="color:rgb(128,0,128)">=&gt;</span><span style="color:rgb(0,70,156)">{(t) =&gt; Math.cos(t * .071 + Math.sin(t * .081))}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">sin3</span><span style="color:rgb(128,0,128)">=&gt;</span><span style="color:rgb(0,70,156)">{(t) =&gt; Math.sin(t * .071 + Math.sin(t * .081))}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">cos4</span><span style="color:rgb(128,0,128)">=&gt;</span><span style="color:rgb(0,70,156)">{(t) =&gt; Math.cos(t * .053 + Math.sin(t * .066) + 1)}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">sin4</span><span style="color:rgb(128,0,128)">=&gt;</span><span style="color:rgb(0,70,156)">{(t) =&gt; Math.sin(t * .053 + Math.sin(t * .066) + 1)}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;vertex </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">6</span><span style="color:rgb(128,0,128)">&quot;</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;area </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">7</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">rangeX</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{[-π/2, π/2]}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">rangeY</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{[0, τ]}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">width</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{129}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">height</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{65}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">expr</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{(emit, θ, ϕ, i, j) =&gt; {
        q1.set(0, 0, Math.sin(θ), Math.cos(θ));
        q2.set(0, Math.sin(ϕ), 0, Math.cos(ϕ));
        q1.multiply(q2);
        emit(q1.x, q1.y, q1.z, q1.w);
      }}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">live</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{false}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">channels</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{4}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;line </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">8</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">color</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(32,128,240)">#3090FF</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;area </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">9</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">rangeX</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{[-π/2, π/2]}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">rangeY</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{[0, τ]}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">width</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{129}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">height</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{65}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">expr</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{(emit, θ, ϕ, i, j) =&gt; {
        q1.set(0, Math.sin(θ), 0, Math.cos(θ));
        q2.set(Math.sin(ϕ), 0, 0, Math.cos(ϕ));
        q1.multiply(q2);
        emit(q1.x, q1.y, q1.z, q1.w);
      }}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">live</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{false}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">channels</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{4}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;line </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">10</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">color</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(32,140,0)">#20A000</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;area </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">11</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">rangeX</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{[-π/2, π/2]}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">rangeY</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{[0, τ]}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">width</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{129}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">height</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{65}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">expr</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{(emit, θ, ϕ, i, j) =&gt; {
        q1.set(Math.sin(θ), 0, 0, Math.cos(θ));
        q2.set(0, 0, Math.sin(ϕ), Math.cos(ϕ));
        q1.multiply(q2);
        emit(q1.x, q1.y, q1.z, q1.w);
      }}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">live</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{false}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">channels</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{4}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;line </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">12</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">color</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(203,32,0)">#DF2000</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;/vertex</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;/stereographic4</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;/clock</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
</span><span style="color:rgb(128,0,128)">&lt;/root</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit"></span></code></pre>

</div></div>

<div class="g8 i2"><div class="pad">

<p>Phew. That's how you make a 4D diagram with Hopf fibration as far as the eye can see. Except it's not actually JSX, that's just me and my pretty-printer pretending.</p>

<h2>Geometry Streaming</h2>

<p>The key is the data itself. It's an array of points mostly, but how that data is laid out and interpreted determines how useful it can be.</p>

<p>Most basic primitives come in fixed size chunks. Particles are single points, lines have two points, triangles have three points. Polygons and polylines have N points. So it made sense to have a <em>tuple of N points</em> be the basic logical unit. You can think in logical pieces of geometry, rather than raw points or individual triangles, unlike GL.</p>

<p>Each primitive maps over data in a standard way. Feed an <code>array</code> of points to a <code>line</code>, you get a polyline. Feed a <code>matrix</code> of points to a <code>surface</code> and you get a grid mesh. Simple. But feed a <code>voxel</code> to a <code>vector</code>, and you get a <em>3D vector field</em>. The general idea is that drawing 1 of something should be as easy as drawing 100×100×100.</p>

</div></div>

<div class="wide full">

  <div class="iframe c">
    <iframe src="/files/mathbox2/iframe-lineup.html?c3d6624d" class="mathbox autosize janky" height="320"></iframe>
  </div>

</div>

<div class="c"></div>

<div class="g8 i2 m1"><div class="pad">

<p>This is particularly useful for custom data expressions, which stream in live or procedural data. They now receive an <code>emit(x, y, z, w)</code> function, for emitting a 4-vector like XYZW or RGBA. This is little more than an inlineable call to fill a <code>floatArray[i++] = x</code>, quite a lot faster than returning an array or object.</p>

</div></div>

<div class="c"></div>

<div class="g8 m2"><div class="pad">

  <div class="iframe c">
    <iframe src="/files/mathbox2/iframe-vector.html?c3d6624d" class="mathbox janky" height="250" style="background: #fff; width: 100%;"></iframe>
  </div>

<pre><code class="language-javascript">mathbox
  .interval({
    expr: function (emit, x, i, t) {
      y = Math.sin(x + t);
      emit(x,  y);
      emit(x, -y);
    },
    width:   64,
    items:    2,
    channels: 2,
  })
  .vector({
    color: 0x3090FF,
    width: 3,
    start: true,
  });
</code></pre>

<div class="c"></div>

<p class="tc"><em>Emitting 64 2D vectors on an interval, 2 points each.</em></p>

<p>More importantly it lets you emit N points in one iteration, which makes the JS expressions themselves feel like geometry shaders. The result feeds into one or more styled drawing ops. The number of emit calls has to be constant, but you can always knock out or mask the excess geometry.</p>

</div></div>

<div class="g4 m3"><div class="pad">

  <pre><code class="language-javascript">emit = switch channels
  when 1 then (x) -&gt;
    array[i++] = x
    ++j
    return

  when 2 then (x, y) -&gt;
    array[i++] = x
    array[i++] = y
    ++j
    return

  when 3 then (x, y, z) -&gt;
    array[i++] = x
    array[i++] = y
    array[i++] = z
    ++j
    return

  when 4 then (x, y, z, w) -&gt;
    array[i++] = x
    array[i++] = y
    array[i++] = z
    array[i++] = w
    ++j
    return</code></pre>

  <div class="c"></div>
  
  <p><em>Both the <code>expr</code>ession and <code>emit</code>ter will be inlined into the stream's iteration loop.</em></p>

</div></div>

<div class="g12"><div class="pad">

  <pre><code class="language-javascript">consume = switch channels
  when 1 then (emit) -&gt;
    emit array[i++]
    ++j
    return

  when 2 then (emit) -&gt;
    emit array[i++], array[i++]
    ++j
    return

  when 3 then (emit) -&gt;
    emit array[i++], array[i++], array[i++]
    ++j
    return

  when 4 then (emit) -&gt;
    emit array[i++], array[i++], array[i++], array[i++]
    ++j
    return</code></pre>

  <div class="c"></div>
    
  <p class="tc m1"><em>Closures of Hanoi</em></p>

</div></div>

<div class="g8 i2"><div class="pad">

<h2>(4-in-1)²</h2>

<p>GPUs can operate on 4×1 vectors and 4×4 matrices, so working with 4D values is natural. Values can also be referenced by 4D indices. With one dimension reserved for the tuples, that leaves us 3 dimensions XYZ. Hence MathBox arrays are 3+1D. This is for <em>width</em>, <em>height</em>, <em>depth</em>, while the tuple dimension is called <em>items</em>. It does what it says on the tin, creating 1D W, 2D W×H and 3D W×H×D arrays of tuples. Each tuple is made of N vectors of up to 4 channels each.</p>

<p>Thanks to cyclic buffers and partial updates, history also comes baked in. You can use a spare dimension as a free time axis, retaining samples on the go. You can <code>.set('history', N)</code> to record a short log of a whole array over time, indefinitely.</p>

<p>All of this is modular: a data source is something that can be sampled by a 4D pointer from GLSL. Underneath, arrays end up packed into a regular 2D float texture, with "items × width" horizontally and "height × depth" vertically. Each 'pixel' holds a 1/2/3/4D point.</p>

<p>Mapping a 4D 'pointer' to the real 2D UV coordinates is just arithmetic, and so are operators like <code>transpose</code> and <code>repeat</code>. You just swap the XY indices and tell everyone downstream that it's now <em>this</em> big instead. They can't tell the difference.</p>

<p>You can create giant procedural arrays this way, including across rectangular texture size limits, as none of them actually exist except as transient values deep inside a GPU core. Until you materialize them by rendering to a texture using the <code>memo</code> primitive. Add in operators like interpolation and convolution and it's a pretty neat real-time finishing kit for data.</p>

</div></div>

<div class="wide full">

  <div class="iframe c">
    <iframe src="/files/mathbox2/iframe-volume.html?c3d6624d" class="mathbox autosize janky" height="320"></iframe>
  </div>

</div>

<div class="c"></div>

<div class="g10 i1 m4"><div class="pad">

<p><img src="/files/mathbox2/too-many-webgls.png" alt="Too many WebGL contexts" /></p>

</div></div>

<div class="g8 i2"><div class="pad">

<p><em><big><a href="/blog/mathbox2-pt2/">Continued in Part 2.</a></big></em></p>

</div></div>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[MathBox²]]></title>
    <link href="https://acko.net/blog/mathbox2-pt2/"/>
    <updated>2015-09-27T00:00:00+02:00</updated>
    <id>https://acko.net/blog/mathbox2-pt2</id>
    <content type="html"><![CDATA[<script src="/files/katex/katex.min.js"></script>

<script src="/files/katex/contrib/auto-render.min.js"></script>

<link rel="stylesheet" type="text/css" href="/files/katex/katex.min.css" />

<div class="g8 i2 first"><div class="pad">

<h2 class="sub">Part 2</h2>

</div></div>

<div class="c"></div>

<div class="g8 i2"><div class="pad">

<a href="/blog/mathbox2/">Continued from Part 1.</a>

<h2>I-Can't-Believe-It's-Not-React</h2>

<p>Underneath sits a large codebase driving it, 200+ files in JS/CS alone, not including dependencies that aren't my own. Much of it is infrastructure necessary to pull off certain tricks consistently: you can draw 2.5D lines with grace, render arbitrary Unicode text in GL, sync HTML to GPU-computed geometry, and do all this with GLSL code composed on the fly, including your own. Nobody needs all of this.</p>

</div></div>

<div class="wide full">

  <div class="iframe c">
    <iframe src="/files/mathbox2/iframe-labels.html?c3d6624d" class="mathbox autosize janky" height="320"></iframe>
  </div>

</div>

<aside class="g8 i2"><div class="pad">

    <ul>
    <li><span style="color: rgb(48,192,255)">LaTeX HTML with KaTeX.</span></li>
    <li><span style="color: rgb(48,144,255)">Plain HTML DOM.</span></li>
    <li><span style="color: rgb(192,64,0)">Virtual HTML with DOM diff.</span></li>
    <li>Live Signed Distance Fields for GL.</li>
    </ul>
  
</div></aside>

<div class="g8 i2"><div class="pad">

<p>These wildly different strategies are actually all abstracted into the DOM path or the GL path, with a quacks-like-React component as the glue in the DOM path.</p>

</div></div>

<div class="g5"><div class="pad">

<pre><code class="language-jsx wrap small"><span style="color:rgb(128,0,128)">&lt;root&gt;
  <span style="color:rgb(0,70,156)">&lt;!-- Place the camera --&gt;</span>
  &lt;camera /&gt;
  <span style="color:rgb(0,70,156)">&lt;!-- 3D Polar view --&gt;</span>
  &lt;polar&gt;
      <span style="color:rgb(0,70,156)">&lt;!-- Sample an interval --&gt;</span>
      &lt;interval /&gt;
      <span style="color:rgb(0,70,156)">&lt;!-- Draw a line --&gt;</span>
      &lt;line /&gt;
      <span style="color:rgb(0,70,156)">&lt;!-- Resample along length --&gt;</span>
      &lt;resample /&gt;
      <span style="color:rgb(0,70,156)">&lt;!-- Draw a set of points --&gt;</span>
      &lt;point /&gt;
      <span style="color:rgb(0,70,156)">&lt;!-- Make HTML labels --&gt;</span>
      &lt;html /&gt;
      <span style="color:rgb(0,70,156)">&lt;!-- Draw HTML sprites --&gt;</span>
      &lt;dom /&gt;

      <span style="color:rgb(0,70,156)">...</span>      

      <span style="color:rgb(0,70,156)">&lt;!-- Sample an interval --&gt;</span>
      &lt;interval /&gt;
      <span style="color:rgb(0,70,156)">&lt;!-- Draw a line --&gt;</span>
      &lt;line /&gt;
      <span style="color:rgb(0,70,156)">&lt;!-- Resample along length --&gt;</span>
      &lt;resample /&gt;
      <span style="color:rgb(0,70,156)">&lt;!-- Draw a set of points --&gt;</span>
      &lt;point /&gt;
      <span style="color:rgb(0,70,156)">&lt;!-- Make GL text --&gt;</span>
      &lt;text /&gt;
      <span style="color:rgb(0,70,156)">&lt;!-- Generate colors --&gt;</span>
      &lt;array /&gt;
      <span style="color:rgb(0,70,156)">&lt;!-- Draw GL sprites --&gt;</span>
      &lt;label /&gt;

    &lt;/vertex&gt;
  &lt;/cartesian&gt;
&lt;/root&gt;</span></code></pre>

</div></div>

<aside class="g7"><div class="pad">

<pre><code class="language-javascript wrap small">// Define VDOM handler to clone real DOM elements
var clone = MathBox.DOM.createClass({
  render: function (el, props, children) {
    var element = children.cloneNode(true);
    return element;
  },
});

// Define VDOM handler to format 'latex' into an HTML span
var latex = MathBox.DOM.createClass({
  render: function (el) {
    this.props.innerHTML = katex.renderToString(this.children);
    return el('span', this.props);
  }
});</code></pre>

<div class="c"></div>

<p class="tc"><em>The interface with the HTML DOM.</em></p>

<p class="tc"><em>Appearances deceive however,<br />as MathBox's own DOM is an entirely different beast.</em></p>

</div></aside>

<div class="g8 i2"><div class="pad">

<p>Most of the code is for initialization only, building up a reactive machine by combining components. Once assembled it lets the GPU do most of the crunching, while relying on the JS VM to inline and optimize the chewy outside.</p>

<p>At the top level, MathBox is plain old JavaScript, used like this:</p>

</div></div>

<div class="g8"><div class="pad">
  <pre><code class="language-javascript" tabindex="0">/* Easy Mode */

// Bootstrap MathBox and Three.js
var mathbox = mathBox();
if (mathbox.fallback) { throw Error("No WebGL support.") };

// Make MathBox primitives
var view =
  mathbox
  .set({
    scale: null,
  })
  .camera({
    proxy: true,
    position: [0, 0, 3]
  })
  .polar({
    range: [[-2, 2], [-1, 1], [-1, 1]],
    scale: [2, 1, 1],
    bend: .25
  });

view.interval({
  width: 48,
  expr: function (emit, x, i, t) {
    // Emit sine wave
    y = Math.sin(x + t / 4) * .5 + .75;
    emit(x, y);
  },
  channels: 2,
})
.line({
  color: 0x30C0FF,
  width: 16,
})
.resample({
  width: 8,
})
.point({
  color: 0x30C0FF,
  size: 60,
})
.html({
  width:  8,
  expr: function (emit, el, i, j, k, l, t) {
    // Emit random latex
    var color = ['#30D0FF','#30A0FF'][i%2];
    var a = Math.round(t + i) % symbols.length;
    var b = Math.round(Math.sqrt(t * t + Math.sin(t + i * i) + 5));
    emit(el(latex, {style: {color: color}},
      '\\sqrt{\\text{LaTeX} + '+(i + b)+' \\pi^{'+symbols[a]+'}}'));
  },
})
.dom({
  snap: false,
  offset: [0, 32],
  depth: 0,
  zoom: 1,
  outline: 2,
  size: 20,
});

// ...
</code></pre>

</div></div>

<aside class="g4 m1">

  <div class="iframe c">
    <iframe src="/files/mathbox2/iframe-splash.html?c3d6624d" class="mathbox janky" height="250" style="background: #fff; width: 100%;"></iframe>

    <p class="tc">CSS 3D rims included.</p>
  </div>

</aside>

<aside class="g4">

  <div class="iframe c">
    <iframe src="/files/mathbox2/iframe-fallback.html?c3d6624d" class="mathbox janky" height="250" style="background: #fff; width: 100%;"></iframe>

    <p class="tc">The default fallback message.</p>
  </div>

</aside>

<div class="g8 i2"><div class="pad">
  
<p>The WebGL Canvas bootstrapper is a separate piece though, it's wrapping <a href="https://github.com/unconed/threestrap" target="_blank">Threestrap</a>, the little non-framework that could. It lets you spawn a fully functioning GL canvas in one exceedingly configurable call. It takes care of browser support, resizing with retina, CSS alignment, warm-up and more.</p>

<p>If you prefer instead, you can spawn a bare MathBox context in a Three.js scene of your choosing, but you'll have to babysit it:</p>

</div></div>

<div class="g12"><div class="pad">
  <pre><code class="language-javascript" tabindex="1">/* Simple Mode */

// Vanilla Three.js
var renderer = new THREE.WebGLRenderer();
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, WIDTH / HEIGHT, .01, 1000);

// Insert into document
document.body.appendChild(renderer.domElement);

// MathBox context
var context = new MathBox.Context(renderer, scene, camera).init();
var mathbox = context.api;

// Set size
renderer.setSize(WIDTH, HEIGHT);
context.resize({ viewWidth: WIDTH, viewHeight: HEIGHT });

// Place camera and set background
camera.position.set(0, 0, 3);
renderer.setClearColor(new THREE.Color(0xFFFFFF), 1.0);

// MathBox elements
view = mathbox
.set({
  focus: 3,
})
.cartesian({
  range: [[-2, 2], [-1, 1], [-1, 1]],
  scale: [2, 1, 1],
});
// ...

// Render frames
var frame = function () {
  requestAnimationFrame(frame);
  context.frame();
  renderer.render(scene, camera);
};
requestAnimationFrame(frame);</code></pre>
</div></div>

<div class="g8 i2 m1"><div class="pad">

<p>Despite looking like a monolith, it really isn't, it was merely a matter of convenience and sanity to not decouple it more until its shape had stabilized. Minimal builds are, for now, left as an exercise to the reader. I've split up the thinking and design into several articles, mirroring the architecture. However, you don't need to know all this to <em>use</em> MathBox 2, they are for people who want to know the how and why... the <a href="/blog/a-dom-for-robots/">document model</a>, the <a href="/blog/yak-shading/">geometry core</a> and the <a href="/blog/shadergraph-2/">shader assembly</a>.</p>

<p>In putting it all together, the devil's in the details of course. Depending on your imagination, it's either much more or much less powerful than you want. There's still far too much to cover: slideshows, keyframe tracks, fov-calibrated units, z-indexes, atlas retexting, … Most of this is unsurprising in that it all works. You can define a keyframe interpolation between two value or emitter expressions, and watch the smoothly lerped data go. Animation tracks are tied to triggers like clocks and slides, which lets them fit naturally in presentations.</p>

</div></div>

<div class="c"></div>

<div class="g10 i1"><div class="pad">

<pre><code class="language-jsx wrap small"><span style="color:rgb(128,0,128)">&lt;root&gt;
  <span style="color:rgb(0,70,156)">&lt;!-- Present slides --&gt;</span>
  &lt;present <span style="color:rgb(144,64,0)">index</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{1}</span>&gt;
    <span style="color:rgb(0,70,156)">&lt;!-- Slide 1 --&gt;</span>
    &lt;slide <span style="color:rgb(144,64,0)">steps</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{2}</span>&gt;
      <span style="color:rgb(0,70,156)">&lt;!-- Transition effect --&gt;</span>
      &lt;reveal&gt;
        <span style="color:rgb(0,70,156)">&lt;!-- Sample an interval --&gt;</span>
        &lt;interval /&gt;
        <span style="color:rgb(0,70,156)">&lt;!-- Draw a line --&gt;</span>
        &lt;line /&gt;
        <span style="color:rgb(0,70,156)">&lt;!-- Step through keyframes --&gt;</span>
        &lt;step <span style="color:rgb(144,64,0)">script</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{[
            {props: {color: 'red'}},
            {props: {color: 'blue'}},
          ]}</span> &gt;
      &lt;/reveal&gt;
    &lt;/slide&gt;
    <span style="color:rgb(0,70,156)">&lt;!-- Slide 2 --&gt;</span>
    &lt;slide&gt;
      <span style="color:rgb(0,70,156)">&lt;!-- Transition effect --&gt;</span>
      &lt;move&gt;
        <span style="color:rgb(0,70,156)">&lt;!-- Sample an interval --&gt;</span>
        &lt;interval /&gt;
        <span style="color:rgb(0,70,156)">&lt;!-- Play through keyframes --&gt;</span>
        &lt;play <span style="color:rgb(144,64,0)">script</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{[
                       {props: {expr: (emit, x) =&gt; emit(x, Math.sin(x))}},
                       {props: {expr: (emit, x) =&gt; emit(x, Math.tan(x))}},
                      ]}</span> /&gt;
        <span style="color:rgb(0,70,156)">&lt;!-- Draw a line --&gt;</span>
        &lt;line /&gt;

        <span style="color:rgb(0,70,156)">&lt;!-- Slide 2.1 --&gt;</span>
        &lt;slide&gt;
        &lt;/slide&gt;
      &lt;/move&gt;
    &lt;/slide&gt;
    <span style="color:rgb(0,70,156)">&lt;!-- Slide 3 --&gt;</span>
    &lt;slide&gt;
      <span style="color:rgb(0,70,156)">&lt;!-- Transition effect --&gt;</span>
      &lt;reveal&gt;
        <span style="color:rgb(0,70,156)">&lt;!-- Sample an interval --&gt;</span>
        &lt;interval /&gt;
        <span style="color:rgb(0,70,156)">&lt;!-- Draw a line --&gt;</span>
        &lt;line /&gt;
      &lt;/reveal&gt;
    &lt;/slide&gt;
  &lt;/present&gt;
&lt;/root&gt;</span></code></pre>

</div></div>

<div class="g8 i2"><div class="pad">

<h2>One More Thing…</h2>

<p class="tc"><img src="/files/mathbox2/fridge.png" style="max-width: 30%; margin: 0 auto;" class="flat" alt="Fridge is cool" title="Fridge is cool" /></p>

<p>Images are data. So is audio. That means MathBox 2 is Winamp AVS, Milkdrop and the mythical Fridge all rolled into one. You can replicate your everyday trippy music visualizer with two operators: render-to-texture (<code>rtt</code>) and <code>compose</code>. It acts as an embedded scene, rendering all of its children to an off-screen image, while Compose renders a full-screen pass. This is where the model's expressiveness shines.</p>

<p>Milkdrop equals <code>mathbox.rtt(…).compose(…).….end().compose(…)</code>, that is, an image feeding back into itself, but also rendered to the screen. The necessary double buffering and swaps are abstracted away. Drop in shapes and shaders, add transforms, nest as you like. RTTs have a <code>history</code> parameter like arrays, so Turing patterns, self-propagating hypno spirals, and other cool partial diffy eqs are a shader away.</p>

</div></div>

<div class="c"></div>

<div class="wide full m2">

  <div class="iframe c">
    <iframe src="/files/mathbox2/iframe-rtt-history.html?c3d6624d" class="mathbox autosize janky" height="320"></iframe>
  </div>

</div>

<div class="c"></div>

<div class="g12"><div class="pad">
  <!--rtt history-->

<pre><code class="language-jsx wrap small"><span></span><span style="color:rgb(128,0,128)">&lt;root </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">1</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">scale</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{720}</span><span style="color:rgb(128,0,128)"></span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;rtt </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">render</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">minFilter</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">nearest</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">magFilter</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">nearest</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">type</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">unsignedByte</span><span style="color:rgb(128,0,128)">&quot;</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;camera </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">3</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">lookAt</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{[0, 0, 0]}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">position</span><span style="color:rgb(128,0,128)">=&gt;</span><span style="color:rgb(0,70,156)">{(t) =&gt; { return [Math.cos(t) * 3, 0, Math.sin(t) * 3] }}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;cartesian </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">4</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">range</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{[[-2, 2], [-1, 1], [-1, 1]]}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">scale</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{[2, 1, 1]}</span><span style="color:rgb(128,0,128)"></span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;transform </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">5</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">scale</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{[7/10, 7/10, 7/10]}</span><span style="color:rgb(128,0,128)"></span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;grid </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">6</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">width</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{5}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">divideX</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{2}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">divideY</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{2}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">zBias</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{10}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">opacity</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{1/4}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">color</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{16768992}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;/transform</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;/cartesian</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;/rtt</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;rtt </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">rtt1</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">history</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{4}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">type</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">unsignedByte</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">minFilter</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">linear</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">magFilter</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">linear</span><span style="color:rgb(128,0,128)">&quot;</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;shader </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">8</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">code</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">
uniform vec3 dataResolution;
uniform vec3 dataSize;
uniform float cosine;
uniform float sine;
vec4 getSample(vec3 xyz);
vec4 getFramesSample(vec3 xyz) {
  vec2 pos = xyz.xy * dataResolution.xy - .5;
  pos = ((pos * dataSize.xy) * mat2(cosine, sine, -sine, cosine) * .999) / dataSize.xy;
  xyz.xy = (pos + .5) * dataSize.xy;
  vec4 c = getSample(xyz + vec3( 0.0, 0.0, 1.0));
  vec3 t = getSample(xyz + vec3( 0.0, 1.5, 0.0)).xyz;
  vec3 b = getSample(xyz + vec3( 0.0,-1.5, 0.0)).xyz;
  vec3 l = getSample(xyz + vec3(-1.5, 0.0, 0.0)).xyz;
  vec3 r = getSample(xyz + vec3( 1.5, 0.0, 0.0)).xyz;
  return vec4((t + b + l + r) / 2.0 - c.xyz, c.w);
}</span><span style="color:rgb(128,0,128)">&quot;
 </span><span style="color:rgb(144,64,0)">cosine</span><span style="color:rgb(128,0,128)">=&gt;</span><span style="color:rgb(0,70,156)">{(t) =&gt; Math.cos(Math.sin(t * .2) * .005)}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">sine</span><span style="color:rgb(128,0,128)">=&gt;</span><span style="color:rgb(0,70,156)">{(t) =&gt; Math.sin(Math.sin(t * .2) * .005)}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;resample </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">resample1</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">indices</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{3}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">channels</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{4}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;compose </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">10</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;compose </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">11</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">source</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">#render</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">blending</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">add</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;/rtt</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;rtt </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">12</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">minFilter</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">linear</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">magFilter</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">linear</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">type</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">unsignedByte</span><span style="color:rgb(128,0,128)">&quot;</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;shader </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">colormap</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">code</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">
uniform float modulate1;
uniform float modulate2;
uniform float modulate3;
uniform float modulate4;
vec4 getSample(vec3 xyz);
vec4 getFramesSample(vec3 xyz) {
  vec4 color = (
    getSample(xyz) +
    getSample(xyz + vec3(0.0, 0.0, 1.0)) +
    getSample(xyz + vec3(0.0, 0.0, 2.0)) +
    getSample(xyz + vec3(0.0, 0.0, 3.0))
  ) / 4.0;
  color = color * color * color * 1.15;
  float v = color.x + color.y + color.z;
  vec3 c = vec3(v*v + color.x * .2, v*v, v*v*v + color.z) * .333;
  c = mix(c, mix(sqrt(c.yzx * c), c.zxy, modulate1), modulate2);
  c = mix(c, mix(c.yzx, c.zxy, modulate1), modulate2);
  c = mix(c, mix(abs(sin(c.yxz * 2.0)), c.zyx, modulate3), modulate4);
  return vec4(c, 1.0);
}</span><span style="color:rgb(128,0,128)">&quot;
 </span><span style="color:rgb(144,64,0)">modulate1</span><span style="color:rgb(128,0,128)">=&gt;</span><span style="color:rgb(0,70,156)">{(t) =&gt; Math.cos(t * .417) * .5 + .5}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">modulate2</span><span style="color:rgb(128,0,128)">=&gt;</span><span style="color:rgb(0,70,156)">{(t) =&gt; Math.cos(t * .617 + Math.sin(t * .133)) * .5 + .5}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">modulate3</span><span style="color:rgb(128,0,128)">=&gt;</span><span style="color:rgb(0,70,156)">{(t) =&gt; Math.cos(t * .217 + 2.0) * .5 + .5}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">modulate4</span><span style="color:rgb(128,0,128)">=&gt;</span><span style="color:rgb(0,70,156)">{(t) =&gt; Math.cos(t * .117 + 3.0 + Math.sin(t * .133)) * .5 + .5}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;resample </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">resample2</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">source</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">#rtt1</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">indices</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{3}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">channels</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{4}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;compose </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">15</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;/rtt</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;shader </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">16</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">code</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">
vec4 getSample(vec2 xy);
vec4 getFramesSample(vec2 xy) {
  return getSample(xy + vec2(0.5, 0.5));
}</span><span style="color:rgb(128,0,128)">&quot;
 </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;resample </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">resample3</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">indices</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{2}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">channels</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{4}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;compose </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">18</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">source</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">#resample2</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
</span><span style="color:rgb(128,0,128)">&lt;/root</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit"></span></code></pre>

</div></div>

<div class="c"></div>

<div class="g8 i2 m2"><div class="pad">

<p>The difference compared to AVS is that the <code>.rtt()</code> is inert to its container by default. Until you add on a <code>.compose()</code> pass, it's just a dangling data source. Meanwhile the <code>.compose()</code> op offers the necessary  GL blend modes, opacity and color tints through style properties. Document order defines drawing order, so the decomposition into render passes is direct. On top, <code>zOrder</code> can be overridden (drawing order), as can <code>zIndex</code> (2D stacking order) and <code>zBias</code> (3D stacking order).</p>

<p>You can nest effects and compose shaders to create recursive visualizations, sampling from themselves or each other:</p>

</div></div>

<div class="c"></div>

<div class="wide full m2">

  <div class="iframe c">
    <iframe src="/files/mathbox2/iframe-readyornot.html?c3d6624d" class="mathbox autosize janky" height="320" allowfullscreen="allowfullscreen"></iframe>
  </div>
  
  <div class="c"></div>
  
  <p class="tc"><a href="http://acko.net/files/mathbox2/iframe-readyornot.html?1" target="_blank">Open in New Window</a><br /><em>(Burn that fillrate, baby.)</em></p>
  
  <p class="tc m2">Bonus: <a href="http://fm.acko.net/" target="_blank">Endless Visualizer</a>.</p>
</div>

<div class="g12"><div class="pad">


  <!--vertex feedback-->
<pre><code class="language-jsx wrap small"><span></span><span style="color:rgb(128,0,128)">&lt;root </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">1</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">scale</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{720}</span><span style="color:rgb(128,0,128)"></span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;camera </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">2</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">proxy</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{true}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">position</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{[3/10, 1/10, 2]}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;group </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">3</span><span style="color:rgb(128,0,128)">&quot;</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;array </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">audioTime</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">data</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{[]}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">width</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{1024}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">channels</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{1}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;array </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">audioFreq</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">data</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{[]}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">width</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{512}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">channels</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{1}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;/group</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;rtt </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">render</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">width</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{256}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">height</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{144}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">type</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">unsignedByte</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">minFilter</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">nearest</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">magFilter</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">nearest</span><span style="color:rgb(128,0,128)">&quot;</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;camera </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">7</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">position</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{[0, 0, 5/2]}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;group </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">8</span><span style="color:rgb(128,0,128)">&quot;</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;swizzle </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">9</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">source</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">#audioTime</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">order</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">yx</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;spread </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">10</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">width</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{[861/250, 0, 0, 0]}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;shader </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">11</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">code</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">
vec4 getSample(vec4 xyzw);
vec4 getColor(vec4 xyzw) {
  float h = getSample(xyzw).y;
  return vec4(vec3(h), 1.0);
}</span><span style="color:rgb(128,0,128)">&quot;
 </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;resample </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">12</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;transform </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">13</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">scale</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{[1, 3/4, 1]}</span><span style="color:rgb(128,0,128)"></span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;line </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">14</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">points</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">&lt;&lt;</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">colors</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">&lt;</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">width</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{5}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">color</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{16777215}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">opacity</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{2/5}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">blending</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">add</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;/transform</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;/group</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;cartesian </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">15</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">range</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{[[-2, 2], [-1, 1], [-1, 1]]}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">scale</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{[1/2, 1/4, 1/4]}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">quaternion</span><span style="color:rgb(128,0,128)">=&gt;</span><span style="color:rgb(0,70,156)">{(t) =&gt; {
          c = Math.cos(t / 3);
          s = Math.sin(t / 3);
          c2 = Math.cos(t / 8.71);
          s2 = Math.sin(t / 8.71);
          return [s * s2, s * c2, .2, c];
        }}</span><span style="color:rgb(128,0,128)"></span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;grid </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">16</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">divideX</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{4}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">divideY</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{4}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">zBias</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{10}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">opacity</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{1/10}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">color</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{16768992}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">width</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{6}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;/cartesian</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;/rtt</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;rtt </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">rtt1</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">history</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{4}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">width</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{256}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">height</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{144}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">type</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">unsignedByte</span><span style="color:rgb(128,0,128)">&quot;</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;shader </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">18</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">code</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">#map-rotate</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;resample </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">resample1</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">indices</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{3}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">channels</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{4}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;compose </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">20</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">color</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">#ffffff</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">zWrite</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{false}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;compose </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">21</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">source</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">#render</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">blending</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">add</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">color</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">#ffffff</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">zWrite</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{false}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;/rtt</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;rtt </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">rtt2</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">width</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{256}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">height</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{144}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">type</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">float</span><span style="color:rgb(128,0,128)">&quot;</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;camera </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">23</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">position</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{[0, 0, 5/2]}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;clock </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">24</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">seek</span><span style="color:rgb(128,0,128)">=&gt;</span><span style="color:rgb(0,70,156)">{(t) =&gt; audio ? audio.currentTime : t}</span><span style="color:rgb(128,0,128)"></span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;shader </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">25</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">code</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">#map-temporal-blur</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">time</span><span style="color:rgb(128,0,128)">=&gt;</span><span style="color:rgb(0,70,156)">{(t) =&gt; t * 16.0}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">modulate</span><span style="color:rgb(128,0,128)">=&gt;</span><span style="color:rgb(0,70,156)">{(t) =&gt; {
            var bang = ((t &gt; 69.229311)  &amp;&amp; (t &lt; 88.922656)) ||
                       ((t &gt; 88.922656)  &amp;&amp; (t &lt; 148.9143)) ||
                       ((t &gt; 148.9143)   &amp;&amp; (t &lt; 158.2)) ||
                       ((t &gt; 168.284427) &amp;&amp; (t &lt; 188.00)) ? 1 : 0;
            if ((t &gt; 88.922656)  &amp;&amp; (t &lt; 148.9143)) {
              bang *= .5 + .45 * Math.cos(t / 3);
            }
            if ((t &gt; 168)) {
              bang *= .85 + .15 * Math.cos(t);
            }
            modulate = modulate + (bang - modulate) * .1;
            return modulate;
          }}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">pattern</span><span style="color:rgb(128,0,128)">=&gt;</span><span style="color:rgb(0,70,156)">{(t) =&gt; {
            var bang = ((t &gt; 88.922656) &amp;&amp; (t &lt; 148.9143));
            pattern = pattern + (bang - pattern) * .1;
            if ((t &gt; 168)) {
              pattern = .5 + .4 * Math.cos(t * 2.311);
            }
            return pattern;
          }}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">warp</span><span style="color:rgb(128,0,128)">=&gt;</span><span style="color:rgb(0,70,156)">{(t) =&gt; {
            var bang = (t &gt; 148.9143);
            if ((t &gt; 168)) {
              warp *= 1 + .5 * Math.cos(t * .556);
            }
            if ((t &gt; 148.2) &amp;&amp; (t &lt; 158.2)) warp = warp + .75 + .25 * Math.cos((t - 158.2));
            warp = warp + (bang - warp) * .1;
            return warp;
          }}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">shift</span><span style="color:rgb(128,0,128)">=&gt;</span><span style="color:rgb(0,70,156)">{(t) =&gt; {
            var bang = (t &gt; 168) ? Math.max(0, Math.min(1, .1 * (t - 168))) : 0;
            bang *= .75 + .25 * Math.cos(t * .731);
            warp = warp + (bang - warp) * .1;
            return warp;
          }}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;resample </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">resample2</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">source</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">#rtt1</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">indices</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{3}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">channels</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{4}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;compose </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">27</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">color</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">#fff</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">zWrite</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{false}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;/clock</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;transform </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">28</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">scale</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{[1, 1/4, 1]}</span><span style="color:rgb(128,0,128)"></span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;swizzle </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">29</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">source</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">#audioTime</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">order</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">yx</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;spread </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">30</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">width</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{[861/250, 0, 0, 0]}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;shader </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">31</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">code</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">
vec4 getSample(vec4 xyzw);
vec4 getColor(vec4 xyzw) {
  float h = getSample(xyzw).y;
  return vec4(vec3(h) * .2, 1.0);
}</span><span style="color:rgb(128,0,128)">&quot;
 </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;resample </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">32</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;line </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">33</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">points</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">&lt;&lt;</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">colors</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">&lt;</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">width</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{50}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">color</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{16777215}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">opacity</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{1}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">blending</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">add</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;/transform</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;/rtt</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;resample </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">34</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">width</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{129}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">height</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{73}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;repeat </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">lerp</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">depth</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{2}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;shader </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">36</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">code</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">#map-xy-to-xyz</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;resample </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">37</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">indices</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{3}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">channels</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{3}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;transpose </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">transpose</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">order</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">xywz</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;transpose </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">color</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">source</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">#lerp</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">order</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">xywz</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;clock </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">40</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">seek</span><span style="color:rgb(128,0,128)">=&gt;</span><span style="color:rgb(0,70,156)">{(t) =&gt; audio ? audio.currentTime : t}</span><span style="color:rgb(128,0,128)"></span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;clock </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">disco</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">speed</span><span style="color:rgb(128,0,128)">=&gt;</span><span style="color:rgb(0,70,156)">{(t) =&gt; {
        var bang = ((t &gt; 69.329311)  &amp;&amp; (t &lt; 89.122656)) ||
                   ((t &gt; 148.9143)   &amp;&amp; (t &lt; 158.0)) ||
                   ((t &gt; 168.284427) &amp;&amp; (t &lt; 188.077772));
        return bang ? 1 : .2;
      }}</span><span style="color:rgb(128,0,128)"></span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;shader </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">42</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">code</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">#map-z-to-color</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">modulate1</span><span style="color:rgb(128,0,128)">=&gt;</span><span style="color:rgb(0,70,156)">{(t) =&gt; Math.cos((t + 1) * .417) * .5 + .5}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">modulate2</span><span style="color:rgb(128,0,128)">=&gt;</span><span style="color:rgb(0,70,156)">{(t) =&gt; Math.cos((t + 1) * .617 + Math.sin(t * .133)) * .5 + .5}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">modulate3</span><span style="color:rgb(128,0,128)">=&gt;</span><span style="color:rgb(0,70,156)">{(t) =&gt; Math.cos((t + 1) * .217 + 2.0) * .5 + .5}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">modulate4</span><span style="color:rgb(128,0,128)">=&gt;</span><span style="color:rgb(0,70,156)">{(t) =&gt; Math.cos((t + 1) * .117 + 3.0 + Math.sin(t * .133)) * .5 + .5}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;resample </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">color1</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">source</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">#lerp</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">indices</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{2}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">channels</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{4}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;shader </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">44</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">code</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">#map-z-to-color-2</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">modulate1</span><span style="color:rgb(128,0,128)">=&gt;</span><span style="color:rgb(0,70,156)">{(t) =&gt; Math.cos((t + 1) * .417) * .5 + .5}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">modulate2</span><span style="color:rgb(128,0,128)">=&gt;</span><span style="color:rgb(0,70,156)">{(t) =&gt; Math.cos((t + 1) * .617 + Math.sin(t * .133)) * .5 + .5}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">modulate3</span><span style="color:rgb(128,0,128)">=&gt;</span><span style="color:rgb(0,70,156)">{(t) =&gt; Math.cos((t + 1) * .217 + 2.0) * .5 + .5}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">modulate4</span><span style="color:rgb(128,0,128)">=&gt;</span><span style="color:rgb(0,70,156)">{(t) =&gt; Math.cos((t + 1) * .117 + 3.0 + Math.sin(t * .133)) * .5 + .5}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;resample </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">color2</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">source</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">#lerp</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">indices</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{2}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">channels</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{4}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;/clock</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;cartesian </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">46</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">range</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{[[-1.7788, 1.7788], [-1, 1], [-1, 1]]}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">scale</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{[16/9, 1, 1]}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">quaternion</span><span style="color:rgb(128,0,128)">=&gt;</span><span style="color:rgb(0,70,156)">{(t) =&gt; {
        t = t / 3;
        c = Math.cos(t / 4);
        s = Math.sin(t / 4);
        c2 = Math.cos(t / 11.71) * 1.71;
        s2 = Math.sin(t / 11.71) * 1.71;
        return [s * s2, s * c2, -.2, c];
      }}</span><span style="color:rgb(128,0,128)"></span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;lerp </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">47</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">source</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">#transpose</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">width</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{33}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">height</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{19}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;lerp </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">48</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">source</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">#color2</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">width</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{33}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">height</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{19}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;transform </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">49</span><span style="color:rgb(128,0,128)">&quot;</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;line </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">50</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">points</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">&lt;&lt;</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">colors</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">&lt;</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">color</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">#ffffff</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">width</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{2}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">zBias</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{5}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;/transform</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;play </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">51</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">script</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{<span></span>{19: {position: [0, 0, 0]}, 39: {position: [0, 0, 2]}, 57: {position: [0, 0, 0]}}}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;transpose </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">52</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">source</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">&lt;&lt;</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">order</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">yxzw</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;transpose </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">53</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">source</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">&lt;&lt;</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">order</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">yxzw</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;transform </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">54</span><span style="color:rgb(128,0,128)">&quot;</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;line </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">55</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">points</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">&lt;&lt;</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">colors</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">&lt;</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">color</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">#ffffff</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">width</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{2}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">zBias</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{5}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;/transform</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;play </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">56</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">script</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{<span></span>{19: {position: [0, 0, 0]}, 39: {position: [0, 0, -2]}, 57: {position: [0, 0, 0]}}}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;transform </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">57</span><span style="color:rgb(128,0,128)">&quot;</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;point </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">58</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">points</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">&lt;&lt;</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">colors</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">&lt;</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">color</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">#ffffff</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">size</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{10}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">zBias</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{5}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">zOrder</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{1}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">blending</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">add</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">zWrite</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{false}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;/transform</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;play </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">59</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">script</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{<span></span>{19: {position: [0, 0, 0]}, 39: {position: [0, 0, -1]}, 57: {position: [0, 0, 0]}}}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;transform </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">60</span><span style="color:rgb(128,0,128)">&quot;</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;point </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">61</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">points</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">#transpose</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">colors</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">#color2</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">color</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">#ffffff</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">size</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{5}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">zBias</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{5}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">zOrder</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{1}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">blending</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">add</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">zWrite</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{false}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;/transform</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;play </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">62</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">script</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{<span></span>{9: {position: [0, 0, 0]}, 39: {position: [0, 0, 1]}, 57: {position: [0, 0, 0]}}}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;vector </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">63</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">points</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">#transpose</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">colors</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">#color1</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">color</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">#ffffff</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">start</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{false}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">end</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{false}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">width</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{40}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">opacity</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{3/100}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">blending</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">add</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">zWrite</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{false}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">zOrder</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{-2}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;/cartesian</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;/clock</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
</span><span style="color:rgb(128,0,128)">&lt;/root</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit"></span></code></pre>
  
</div></div>

<div class="c"></div>

<div class="g8 i2 m2"><div class="pad">

<h2>Full Stack</h2>

<p>What's left is basically kicking the tires and fixing the blind spots. As such this is not MathBox 2.0, this is MathBox 2 Alpha 1. It's still rough in the compatibility department, easily letting you exceed GL limits satisfied by only 70-80% of WebGL implementations in the wild, without warning. My own goal for public release was to be able to make another one of <em>those</em> presentations with it, only this time, 100% idiosyncratic MathBox. Result, <a href="http://acko.net/tv/pixelfactory/" target="_blank">The Pixel Factory</a>.</p>

<p>Some people have assumed this talk was another tour-de-force of multi-week autism, but in fact, rebuilding my old slides for v2 was easy and obvious. The RGBA subpixels and their labels are animated lambdas and GLSL. The multi-samples, the depth buffer columns, the tangents and normals, same thing. JavaScript twiddles the knobs while the GPU visualizes the visualizer, and in doing so, itself.</p>

<p><a href="https://gitgud.io/unconed/mathbox" target="_blank">Here the code be</a>.</p>

<p><em>To give it a whirl in your browser, open the <a target="_blank" href="http://jsbin.com/hasuhaw/edit?html,output">JSBin Sandbox</a>. There is a <a target="_blank" href="https://gitgud.io/unconed/mathbox/blob/master/docs/intro.md">quick start introduction</a> and a <a target="_blank" href="https://gitgud.io/unconed/mathbox/blob/master/docs/primitives.md">list of legos</a>.</em></p>

<ul>
  <li><em>MathBox² - PowerPoint Must Die</em></li>
  <li><a href="/blog/a-dom-for-robots/">A DOM for Robots - Modelling Live Data</a></li>
  <li><a href="/blog/yak-shading/">Yak Shading - Data Driven Geometry</a></li>
  <li><a href="/blog/shadergraph-2/">ShaderGraph 2 - Functional GLSL</a></li>
</ul>


</div></div>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[A DOM for Robots]]></title>
    <link href="https://acko.net/blog/a-dom-for-robots/"/>
    <updated>2015-09-27T00:00:00+02:00</updated>
    <id>https://acko.net/blog/a-dom-for-robots</id>
    <content type="html"><![CDATA[<script src="/files/katex/katex.min.js"></script>

<script src="/files/katex/contrib/auto-render.min.js"></script>

<link rel="stylesheet" type="text/css" href="/files/katex/katex.min.css" />

<div class="g8 i2 first"><div class="pad">

<h2 class="sub">Modelling Live Data</h2>

<p>I want to render live 3D graphics based on a declarative data model. That means a choice of shapes and transforms, as well as data sources and formats. I also want to combine them and make live changes. Which sounds kind of <abbr title="Document Object Model">DOMmy</abbr>.</p>

</div></div>

<div class="g8 i2 first"><div class="pad">
  <a href="http://threejs.org/editor/"><img src="/files/mathbox2/three.js-editor.png" alt="Three.js Editor" title="Three.js Editor" /></a>
</div></div>

<div class="g8 i2"><div class="pad">

<p>3D engines don't have <em>Document Object Models</em> though, they have <em>scene graphs</em> and <em>render trees</em>: minimal data structures optimized for rendering output. In Three.js, each tree node is a JS object with properties and children like itself. Composition only exists in a limited form, with a parent's matrix and visibility combining with that of its children. There is no fancy data binding: the renderer loops over the visible tree leaves every frame, passing in values directly to GL calls. Any geometry is uploaded once to GPU memory and cached. If you put in new parameters or data, it will be used to produce the next frame automatically, aside from a <em>needsUpdate</em> bit here and there for performance reasons.</p>

<p>So Three.js is a thin retained mode layer on top of an immediate mode API. It makes it trivial to draw the same thing over and over again in various configurations. That won't do, I want to draw <em>dynamic</em> things with the same ease. I need a richer model, which means wrapping another retained mode layer around it. That could mean observables, data binding, tree diffing, immutable data, and all the other fun stuff nobody can agree on.</p>

<p>However I mostly feed data <em>in</em> and many parameters will end up as shader properties. These are passed to Three as a dictionary of <code>{ type: '…', value: x }</code> objects, each holding a single parameter. Any code that holds a reference to the dictionary will see the same value, as such it acts as a <em>register</em>: you can share it, transparently binding one value to N shaders. This way a single <code>.set('color', 'blue')</code> call on the fringes can instantly affect data structures deep inside the <code>WebGLRenderer</code>, without actually cascading through.</p>

</div></div>

<div class="wide full">

  <div class="iframe c">
    <iframe src="/files/mathbox2/iframe-vertex.html?c3d6624d" class="mathbox autosize janky" height="320"></iframe>
  </div>

</div>

<div class="g8 i2"><div class="pad">
  <img src="/files/mathbox2/mathbox-scene.png" alt="MathBox Three Scene Object" title="MathBox Three Scene" />
</div></div>

<div class="g8 i2"><div class="pad">

<p>I applied this to build a view tree which retains this property, storing all attributes as shareable registers. The Three.js scene graph is reduced to a single layer of <code>THREE.Mesh</code> objects, flattening the hierarchy. Rather than clumsy CSS3D divs which encode matrices as strings, there's binary arrays, GLSL shaders, and highly optimizable JS lambdas.</p>

<p>As long as you don't go overboard with the numbers, it runs fine even on mobile.</p>

</div></div>

<div class="g12 first"><div class="pad">
  <!--<img src="/files/mathbox2/mathbox-dom-vertex.png" alt="MathBox 2 DOM" title="MathBox 2 DOM">-->

<pre><code class="language-jsx wrap small"><span></span><span style="color:rgb(128,0,128)">&lt;root </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">1</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">scale</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{600}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">focus</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{3}</span><span style="color:rgb(128,0,128)"></span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;camera </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">2</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">proxy</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{true}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">position</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{[0, 0, 3]}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;shader </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">3</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">code</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">
uniform float time;
uniform float intensity;

vec4 warpVertex(vec4 xyzw, inout vec4 stpq) {
  xyzw +=   0.2 * intensity * (sin(xyzw.yzwx * 1.91 + time + sin(xyzw.wxyz * 1.74 + time)));
  xyzw +=   0.1 * intensity * (sin(xyzw.yzwx * 4.03 + time + sin(xyzw.wxyz * 2.74 + time)));
  xyzw +=  0.05 * intensity * (sin(xyzw.yzwx * 8.39 + time + sin(xyzw.wxyz * 4.18 + time)));
  xyzw += 0.025 * intensity * (sin(xyzw.yzwx * 15.1 + time + sin(xyzw.wxyz * 9.18 + time)));

  return xyzw;
}</span><span style="color:rgb(128,0,128)">&quot;
 </span><span style="color:rgb(144,64,0)">time</span><span style="color:rgb(128,0,128)">=&gt;</span><span style="color:rgb(0,70,156)">{(t) =&gt; t / 4}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">intensity</span><span style="color:rgb(128,0,128)">=&gt;</span><span style="color:rgb(0,70,156)">{(t) =&gt; {
        t = t / 4;
        intensity = .5 + .5 * Math.cos(t / 3);
        intensity = 1.0 - Math.pow(intensity, 4);
        return intensity * 2.5;
      }}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;reveal </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">4</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">stagger</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{[10, 0, 0, 0]}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">enter</span><span style="color:rgb(128,0,128)">=&gt;</span><span style="color:rgb(0,70,156)">{(t) =&gt; 1.0 - Math.pow(1.0 - Math.min(1,  (1 + pingpong(t))*2), 2)}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">exit</span><span style="color:rgb(128,0,128)">=&gt;</span><span style="color:rgb(0,70,156)">{(t) =&gt; 1.0 - Math.pow(1.0 - Math.min(1,  (1 - pingpong(t))*2), 2)}</span><span style="color:rgb(128,0,128)"></span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;vertex </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">5</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">pass</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">view</span><span style="color:rgb(128,0,128)">&quot;</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;polar </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">6</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">bend</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{1/4}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">range</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{[[-π, π], [0, 1], [-1, 1]]}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">scale</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{[2, 1, 1]}</span><span style="color:rgb(128,0,128)"></span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;transform </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">7</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">position</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{[0, 1/2, 0]}</span><span style="color:rgb(128,0,128)"></span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;axis </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">8</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">detail</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{512}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;scale </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">9</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">divide</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{10}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">unit</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{π}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">base</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{2}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;ticks </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">10</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">width</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{3}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">classes</span><span style="color:rgb(128,0,128)">=[&quot;</span><span style="color:rgb(0,0,192)">foo</span><span style="color:rgb(128,0,128)">&quot;, </span><span style="color:rgb(144,64,0)">&quot;</span><span style="color:rgb(0,0,192)">bar</span><span style="color:rgb(128,0,128)">&quot;] </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;scale </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">11</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">divide</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{5}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">unit</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{π}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">base</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{2}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;format </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">12</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">expr</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{(x) =&gt; {
        return x ? (x / π).toPrecision(2) + '</span><span style="color:rgb(144,64,0)">π</span><span style="color:rgb(0,70,156)">' : 0
      }}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;label </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">13</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">depth</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{1/2}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">zIndex</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{1}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;/transform</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;axis </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">14</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">axis</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{2}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">detail</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{128}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">crossed</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{true}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;transform </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">15</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">position</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{[π/2, 0, 0]}</span><span style="color:rgb(128,0,128)"></span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;axis </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">16</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">axis</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{2}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">detail</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{128}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">crossed</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{true}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;/transform</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;transform </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">17</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">position</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{[-π/2, 0, 0]}</span><span style="color:rgb(128,0,128)"></span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;axis </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">18</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">axis</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{2}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">detail</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{128}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">crossed</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{true}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;/transform</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;grid </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">19</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">divideX</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{40}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">detailX</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{512}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">divideY</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{20}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">detailY</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{128}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">width</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{1}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">opacity</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{1/2}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">unitX</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{π}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">baseX</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{2}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">zBias</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{-5}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;interval </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">20</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">width</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{512}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">expr</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{(emit, x, i, t) =&gt; {
        emit(x, .5 + .25 * Math.sin(x + t) + .25 * Math.sin(x * 1.91 + t * 1.81));
      }}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">channels</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{2}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;line </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">21</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">width</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{5}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;play </span><span style="color:rgb(144,64,0)">id</span><span style="color:rgb(128,0,128)">=&quot;</span><span style="color:rgb(0,0,192)">22</span><span style="color:rgb(128,0,128)">&quot; </span><span style="color:rgb(144,64,0)">pace</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{10}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">loop</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{true}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">to</span><span style="color:rgb(128,0,128)">=</span><span style="color:rgb(0,70,156)">{3}</span><span style="color:rgb(128,0,128)"> </span><span style="color:rgb(144,64,0)">script</span><span style="color:rgb(128,0,128)">=[[</span><span style="color:rgb(0,70,156)">{color: &quot;</span><span style="color:rgb(144,64,0)">rgb(48, 144, 255)</span><span style="color:rgb(0,70,156)">&quot;}</span><span style="color:rgb(128,0,128)">], </span><span style="color:rgb(144,64,0)">[</span><span style="color:rgb(0,70,156)">{color: &quot;</span><span style="color:rgb(144,64,0)">rgb(100, 180, 60)</span><span style="color:rgb(0,70,156)">&quot;}</span><span style="color:rgb(128,0,128)">], </span><span style="color:rgb(144,64,0)">[</span><span style="color:rgb(0,70,156)">{color: &quot;</span><span style="color:rgb(144,64,0)">rgb(240, 20, 40)</span><span style="color:rgb(0,70,156)">&quot;}</span><span style="color:rgb(128,0,128)">], </span><span style="color:rgb(144,64,0)">[</span><span style="color:rgb(0,70,156)">{color: &quot;</span><span style="color:rgb(144,64,0)">rgb(48, 144, 255)</span><span style="color:rgb(0,70,156)">&quot;}</span><span style="color:rgb(128,0,128)">]] </span><span style="color:rgb(144,64,0)">/</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;/polar</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;/vertex</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
 </span><span style="color:rgb(144,64,0)"> </span><span style="color:rgb(144,64,0)"></span><span style="color:rgb(128,0,128)">&lt;/reveal</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit">
</span><span style="color:rgb(128,0,128)">&lt;/root</span><span style="color:rgb(128,0,128)">&gt;</span><span style="color:inherit"></span></code></pre>  
  
  <div class="c"></div>
    
  <p><em>Note: The JSX is a lie, you define nodes in pure JS.</em></p>
</div></div>

<div class="g8 i2"><div class="pad">

<h2>Keep it Simple</h2>

<p>From afar there's a tree of nodes, similar to SVG tags. This is the MathBox library of vector primitives. The basic shapes are all there: points, lines, faces, vectors, surfaces, etc. These nodes are placed inside a shallow hierarchy of views and transforms.</p>

<p>However none of the shapes draw anything by themselves. They only know how to draw <em>data</em> supplied by a linked source. Data can be an array (static or live), a procedural source, custom JS / GLSL code, etc. This is further augmented by data operators which can be sandwiched between source and shape, forming automatic pipelines between siblings.</p>

<p>The current set of components looks like this:</p>

</div></div>

<div class="c"></div>

<div class="g12 m1"><div class="pad">

<div class="columns-4">

  <div class="g3 section">
  <h3>Base</h3>
  <ul>
    <li>Group</li>
    <li>Inherit</li>
    <li>Root</li>
    <li>Unit</li>
  </ul>

  <h3>Camera</h3>
  <ul>
    <li>Camera</li>
  </ul>
  </div>

  <div class="g3 section">
  <h3>Draw</h3>
  <ul>
    <li>Axis</li>
    <li>Face</li>
    <li>Grid</li>
    <li>Line</li>
    <li>Point</li>
    <li>Strip</li>
    <li>Surface</li>
    <li>Ticks</li>
    <li>Vector</li>
  </ul>
  </div>

  <div class="g3 section">
  <h3>Data</h3>
  <ul>
    <li>Area</li>
    <li>Array</li>
    <li>Interval</li>
    <li>Matrix</li>
    <li>Scale</li>
    <li>Volume</li>
    <li>Voxel</li>
  </ul>
  </div>

  <div class="g3 section">
  <h3>Operator</h3>
  <ul>
    <li>Grow</li>
    <li>Join</li>
    <li>Lerp</li>
    <li>Memo</li>
    <li>Resample</li>
    <li>Repeat</li>
    <li>Slice</li>
    <li>Split</li>
    <li>Spread</li>
    <li>Swizzle</li>
    <li>Transpose</li>
  </ul>
  </div>

  <div class="c"></div>

  <div class="g3 section">
  <h3>Overlay</h3>
  <ul>
    <li>DOM</li>
    <li>HTML</li>
  </ul>

  <h3>Present</h3>
  <ul>
    <li>Move</li>
    <li>Play</li>
    <li>Present</li>
    <li>Reveal</li>
    <li>Slide</li>
    <li>Step</li>
  </ul>
  </div>

  <div class="g3 section">
  <h3>RTT</h3>
  <ul>
    <li>Compose</li>
    <li>RTT</li>
  </ul>

  <h3>Shader</h3>
  <ul>
    <li>Shader</li>
  </ul>
  </div>

  <div class="g3 section">
  <h3>Text</h3>
  <ul>
    <li>Format</li>
    <li>Label</li>
    <li>Text</li>
    <li>Retext</li>
  </ul>

  <h3>Time</h3>
  <ul>
    <li>Clock</li>
    <li>Now</li>
  </ul>
  </div>

  <div class="g3 section">
  <h3>Transform</h3>
  <ul>
    <li>Fragment</li>
    <li>Layer</li>
    <li>Transform</li>
    <li>Transform4</li>
    <li>Vertex</li>
  </ul>
  </div>

  <div class="g3 section">
  <h3>View</h3>
  <ul>
    <li>Cartesian</li>
    <li>Cartesian4</li>
    <li>Polar</li>
    <li>Spherical</li>
    <li>Stereographic</li>
    <li>Stereographic4</li>
    <li>View</li>
  </ul>
  </div>

</div>

</div></div>

<div class="c"></div>

<div class="g8 i2 m1"><div class="pad">

<p>To make you feel at home, nodes have an <code>id</code> and <code>classes</code>, and you can use CSS selectors to identify them. Nodes link up with preceding siblings and parents by default, but you can select any node in the tree. This allows for arbitrary graphs, including feedback loops. However all of this is optional: you can also pass in direct node objects or MathBox's own jQuery-like selections. What it doesn't have is a notion of detached document fragments: nodes are immediately inserted on creation.</p>

<p>A node's attributes can be <code>.get()</code> and <code>.set()</code>, though there is also a read-only <code>.props</code> dictionary for fashionable reasons. The values are strongly typed as Three.js colors, vectors, matrices, … but accept e.g. CSS colors and ordinary arrays too. The values are normalized for immediate use, the original values are preserved on the side for printing and serialization.</p>

<p><img src="/files/mathbox2/api.png" alt="MathBox Node API" /></p>

<p>What's unique is the emphasis on time. First, properties can be directly bound to time-dependent expressions, on creation or afterwards. Second, clocks are primitives on their own. This allows for nested timelines, on-demand bullet time, fast forwards and more. It even supports limited time travel, evaluating an expression several frames in the past. This can be used to ensure consistent 60 fps data logging through janky updates, useful for all sorts of things. It's exposed publicly as <code>.bind(key, expr)</code> and <code>.evaluate(key, time)</code> per node. It's also dogfood for declarative animation tracks. The primitives <code>clock</code>/<code>now</code> provide timing, while <code>step</code> and <code>play</code> handle keyframes on tracks.</p>

<p>This is definitely <em>a</em> DOM, but it has only basic features in common with <em>the</em> HTML DOM and does much less. Most of the magic comes from the components themselves. There's no cascade of styles to inherit. Children compose <em>with</em> a parent, they do not inherit <em>from</em> it, only caring about their own attributes. The namespace is clean, with no weird combo styles à la CSS. As much as possible, attributes are unique orthogonal knobs you can turn freely.</p>

<h2>Model-View-Projection</h2>

<p>On the inside I separate the generic data model from the type-specific View Controller attached to it. The controller's job is to create and manage Three.js objects to display the node (if any). Because a data source and a visible shape have very little in common, the nodes and their controllers are blank slates built and organized around named <em>traits</em>. Each trait is a data mix-in, with associated attributes and helpers for common behavior. Primitives with the same traits can be expected to work the same, as their public facing models are identical.</p>

<p>Controllers can traverse the graph to find each other by matching traits, listening for events and making calls in response. This way only specific <em>events</em> will cascade through cause and effect, often skipping large parts of the hierarchy. The only way to do a "global style recalculation" would be to send a forced change event to every single controller, and there's never a reason to do so.</p>

<p>The controller lifecycle is deliberately kept simple: <code>make()</code>, <code>made()</code>, <code>change(…)</code>, <code>unmake()</code>, <code>unmade()</code>. When a model changes, its controller either updates in place, or rebuilds itself, doing an unmake/make cycle. The change handler is invoked on creation as well, to encourage stateless updates. It affords live editing of anything, without having to micro-optimize every possible change scenario. Controllers can also watch bound selectors, retargeting if their matched set changes. This lets primitives link up with elements that have yet to be inserted.</p>

<p>Unlike HTML, the DOM is not forced to contain a render tree as well. Only some of the leaf nodes have styles and create renderables. Siblings and parents are called upon to help, but the effects don't have to be strictly hierarchical. For example, a visual effect can wrap a single leaf but still be applied after all its parents, as transformations are collected and composed in passes.</p>


<h2>It'll Do</h2>

<p>The result is not so much a <em>document model</em> as it is a <em>computational model</em> inside a <em>presentational model</em>. You can feed it finalized data and draw it directly… or you can build new models within it and reproject them live. Memoization enables feedback and meta-visualization. The line between data viz and demo scene is rarely this blurry.</p>

<p>Here, the notion of a <em>computed style</em> has little meaning. Any value will end up being transformed and processed in arbitrary ways down the pipe. As I've <a href="/blog/shadow-dom">tried to explain before</a>, the kinds of things people do with <code>getComputedStyle()</code> and <code>getClientBoundingRect()</code> are better achieved by having an extensible layout model, one that affords custom constraints and composition on an equal footing. To do otherwise is to admit defeat and embrace a leaky abstraction by design.</p>

<p>The shallow hierarchy with composition between siblings is particularly appealing to me, even if I realize it introduces non-traditional semantics more reminiscent of a command-line. It acts as both a jQuery-style chainable API, and a minimal document model. If it offends your sensibilities, you could always defuse the magic by explicitly wiring up every relationship. In case of confusion, <code>.inspect()</code> will log syntax highlighted JSX, while <code>.debug()</code> will draw the underlying shader graphs.</p>

<p>I've defined a good set of basic primitives and iterated on them a few times. But how to implement it, when WebGL doesn't even fully cover OpenGL ES 2?</p>


<ul>
  <li><a href="/blog/mathbox2/">MathBox² - PowerPoint Must Die</a></li>
  <li><em>A DOM for Robots - Modelling Live Data</em></li>
  <li><a href="/blog/yak-shading/">Yak Shading - Data Driven Geometry</a></li>
  <li><a href="/blog/shadergraph-2/">ShaderGraph 2 - Functional GLSL</a></li>
</ul>

</div></div>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Shadow DOM]]></title>
    <link href="https://acko.net/blog/shadow-dom/"/>
    <updated>2014-03-24T00:00:00+01:00</updated>
    <id>https://acko.net/blog/shadow-dom</id>
    <content type="html"><![CDATA[<div class="g8 i2 first"><div class="pad">

<h2 class="sub">SVG, CSS, React and Angular</h2>

</div></div>

<div class="c"></div>

<div class="g8 i2"><div class="pad">

<p>For a while now I've been working on MathBox 2. I want to have an environment where you take a bunch of mathematical legos, bind them to data models, draw them, and modify them interactively at scale. Preferably in a web browser.</p>

<p>Unfortunately HTML is crufty, CSS is annoying and the DOM's unwieldy. Hence we now have libraries like <a href="http://facebook.github.io/react/">React</a>. It creates its own virtual DOM just to be able to manipulate the real one—the Agile Bureaucracy design pattern.</p>

<p>The more we can avoid the DOM, the better. But why? And can we fix it?</p>

</div></div>

<div class="c"></div>

<div class="wide mt2">
  <img src="https://acko.net/files/shadowdom/banner.jpg" alt="Netscape" />
</div>

<div class="c"></div>

<aside class="g3 mt2"><div class="pad">
  <p><img src="https://acko.net/files/shadowdom/star.svg" alt="Star" class="flat" /></p>
</div></aside>

<div class="g8 mt3"><div class="pad">

<pre><code class="language-html" tabindex="0">&lt;?xml version="1.0" encoding="utf-8"?&gt;
&lt;!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"&gt;
&lt;svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
  width="400px" height="400px" viewBox="0 0 400 400" enable-background="new 0 0 400 400" xml:space="preserve"&gt;
  &lt;polygon fill="#FDBD10" stroke="#BE1E2D" stroke-width="3" stroke-miterlimit="10" points="357.803,105.593 276.508,202.82 
    343.855,310.18 226.266,262.91 144.973,360.139 153.592,233.697 36.002,186.426 158.918,155.551 167.538,29.109 234.885,136.469 "/&gt;
  &lt;polygon fill="#FDEB10" points="326.982,114.932 259.695,195.408 315.441,284.271 218.109,245.146 150.821,325.623 157.955,220.966 
    60.625,181.838 162.364,156.283 169.499,51.625 225.242,140.488 "/&gt;
&lt;/svg&gt;
</code></pre>
<div class="c"></div>

</div></div>

<div class="g8 i1 cl"><div class="pad">
  
<h2>Dangling Nodes</h2>

<p>Take SVG. Each XML tag is a graphical shape or instruction. Like all XML, the data has to be serialized into tags with attributes made of strings. Large data sets turn into long string attributes to be parsed. Large collections of stuff turn into many separate tags to be iterated over. Neither is really desirable.</p>

<p>It only represents basic operations, so all serious prep work has to be done by the user up front. This is what D3 is used for, generating and managing more complex mappings for you.</p>

<p>When you put SVG into HTML, each element becomes a full DOM node. A simple <code>&lt;tag&gt;</code> with attributes is now a colossal binding between HTML, JS, CSS and native. It's a JavaScript object that pretends to be an XML tag, embedded inside a layout model that takes years to understand fully.</p>

<p>Its namespace mixes metadata with page layout, getters and setters with plain properties, native methods with JS, string shorthands with nested objects, and so on. Guess how many properties the DOM Node Object actually has in total? We'll be generous and count <code>style</code> as one.</p>

<p>A hundred is not even close. A plain <code>&lt;div&gt;</code> doesn't fare much better. Just serializing a chunk of DOM back into its constituent XML is a tricky task once you get into fun stuff like namespaces. Nothing in the DOM is as simple as <code>JSON.stringify</code>. Why does my polygon have a base URI?</p>

<p>We have all these awesome dev tools now, yet we're using them to teach a terrible model to people who don't know any better.</p>

</div></div>

<aside class="g3 mt2"><div class="pad">
  <p><img src="https://acko.net/files/shadowdom/svg-in-dom.png" alt="SVG in DOM" class="s70" /></p>
</div></aside>

<div class="g8 i2"><div class="pad">

<h2>DOM Shader</h2>

<p>In contrast, there's <a href="http://angularjs.org/">Angular</a>. I like it because they've pulled off a very neat trick: convincing people to adopt a whole new DOM by disguising it as HTML.</p>


<pre><code class="language-html" tabindex="1">&lt;body ng-controller="PhoneListCtrl"&gt;
  &lt;ul&gt;
    &lt;li ng-repeat="phone in phones"&gt;
      {{phone.name}}
      &lt;p&gt;{{phone.snippet}}&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ul&gt;
&lt;/body&gt;
</code></pre>
<div class="c"></div>


<p>When you use <code>&lt;input ng-model="foo"&gt;</code> or <code>&lt;my-directive&gt;</code>, you're creating a controller and a scope, entirely separate from the DOM, with their own rules and chain of inheritance. The pseudo-HTML in the source code is merely an initial definition, most of it inert to the browser. Angular parses it out and replaces much of it.</p>

<p>Like React, the browser's live DOM is subsumed and used as a sort of <em>render tree</em>, a generic canvas to be cleverly manipulated to match a given set of views. The real <em>view tree</em> hides in the shadows of JS, where controllers operate on scopes. They only use the DOM to find each other on creation, and then communicate directly. The DOM is mostly there to trigger events, do layout and look pretty. Form controls are the one exception.</p>

</div></div>

<aside class="g3 i1"><div class="pad">
  <p><img src="https://acko.net/files/shadowdom/svg-in-css.png" alt="SVG in CSS" /></p>
</div></aside>

<div class="g8"><div class="pad">

<p>It's a bad fit because the DOM was built for text markup and there's tons of baggage in the form of inline spans, floats, alignment, indentation, etc. Most of these are layout systems disguised as typography, of which CSS now has several.</p>

<p>The whole idea of cascading styles is suspect. In reality, most styles don't actually cascade: paddings and backgrounds are set on individual elements. The inherited ones are almost all about typography: font styles, text justification, writing direction, word wrap, etc.</p>

<p>Think of it this way: why should a table have a font size? Only the <em>text</em> inside the table can have a font size, the table is just a box with layout that contains other <em>boxes</em>. Why don't we write <code>table text { size: 16px; }</code> instead of <code>table { font-size: 16px; }</code>? Text nodes exist today.</p>

<p>Well because that's how HTML's <code>&lt;font&gt;</code> tag worked. Instead of just making a selector for text nodes, they gave all the other elements font properties. They didn't <em>get rid</em> of font tags, they made them invisible and put one inside each DOM node.</p>

</div></div>

<div class="g8 i2 cl"><div class="pad">

<pre><code class="language-html" tabindex="2">&lt;html&gt;&lt;font&gt;
  &lt;body&gt;&lt;font&gt;
    &lt;h1&gt;&lt;font&gt;Hello World&lt;/font&gt;&lt;/h1&gt;
    &lt;p&gt;&lt;font&gt;Welcome to the future.&lt;/font&gt;&lt;/p&gt;
  &lt;/font&gt;&lt;/body&gt;
&lt;/font&gt;&lt;/html&gt;
</code></pre>
<div class="c"></div>

<h2>Unreasonable Behavior</h2>

<p>It was decided the world would be made of <code>block</code> and <code>inline</code> elements—divs and spans—and they saw that it was good, until someone came along and said, hey, so what about my table?</p>

<pre><code class="language-html" tabindex="3">&lt;table&gt;
  &lt;tr&gt;
    &lt;td&gt;Forever&lt;/td&gt;
    &lt;td&gt;Alone&lt;/td&gt;
  &lt;/tr&gt;
&lt;/table&gt;
</code></pre>
<div class="c"></div>

<p>This <code>&lt;table&gt;</code> can't be replicated with CSS 1. Tables require a particular arrangement of children and apply their own box model. It's a directive posing amongst generic markup, just like Angular.</p>

<p>CSS has never been able to deliver on the promise of turning semantic HTML into arbitrary layout. We've always been forced to add extra divs or classes. These are really just attachment points for independent behaviors.</p>

<p>Purists see these as a taint upon otherwise pristine HTML, even though I've never seen someone close a website because the markup was messy. Not all HTML should be semantic. Rather, HTML stripped of its non-semantic parts should remain meaningful to <em>robots</em>.</p>

<p>CSS 2's solution was instead to make <code>&lt;table&gt;</code> invisible too, to go with the invisible <code>&lt;float&gt;</code>, <code>&lt;layer&gt;</code>, <code>&lt;clear&gt;</code> and <code>&lt;frame&gt;</code> tags which we pretended we didn't have. Watch:</p>

</div></div>

<div class="g7 i1 cl"><div class="pad">

<div class="embed"><blockquote>
  <strong>17.2.1 Anonymous table objects</strong><br /><br />

  […] Any table element will automatically generate necessary anonymous table objects around itself, consisting of at least three nested objects corresponding to a 'table'/'inline-table' element, a 'table-row' element, and a 'table-cell' element. Missing elements generate anonymous objects (e.g., anonymous boxes in visual table layout) according to the following rules […]
</blockquote></div>

</div></div>

<div class="g3"><div class="pad">

  <pre><code class="language-css" tabindex="4">.grid {
  display: table;
}
.grid &gt; ul {
  display: table-row;
}
.grid &gt; ul &gt; li {
  display: table-cell;
}
</code></pre>
<div class="c"></div>

<p class="tc"><em>This is called Not Using Tables.</em></p>

</div></div>

<div class="g8 i2 cl"><div class="pad"> 
  
<p>Without typographical styles, <code>block</code> elements start to look very different. They're styled boxes with implied layout constraints. They stack vertically, expand horizontally and shrink wrap vertically. Floated blocks are boxes that stack horizontally, and shrink wrap both ways. Tables are grids of boxes that are locked together.</p>


<p>Just think how much simpler CSS would be if boxes had box styles and text had text styles, instead of all of them having both. Besides, block margins and paddings don't even work the same on inline elements, there's a whole new layout behavior there.</p>

<p>So we do have two kinds of objects, <em>text and boxes</em>, but several different ways of combining them into layout: inline, stacked, nested, absolute, relative, fixed, floated, flex or table. We have optional behaviors like scrollable, draggable, clipped or overflowing.</p>

<p>They're spread across <code>display</code>, <code>position</code>, <code>float</code> and more, only meaningful in some combinations. And presence is mixed in there too. As a result, you can't unhide an element without knowing its display model. This is a giant red flag.</p>

<h2>Thinking with Portals</h2>
  
<p>It should further raise eyebrows that the binary world of <code>inline</code> and <code>block</code> now also includes a hybrid called <code>inline-block</code>.</p>

<p><img src="https://acko.net/files/shadowdom/medium.png" alt="Medium share thing" /></p>

<p>You generally don't need to embed a contact form–or all of Gmail—in the middle of mixed English/Hebrew poetry shaped like a bird. You just don't. To attach something to flowing text, you should insert an anchor point instead and add floating constraints. Links are called <em>anchor tags</em> for a reason. Why did we forget this?</p>

<p>Don't shove your entire widget right between the words. You'd inherit styles, match new selectors and bubble all your events up through the text just for the sake of binding a pair of (x, y) coordinates.</p>

<p>Heck, pointer events, cursors, hover states... these are for interactive elements only. Why isn't that optional, so mouse events wouldn't need to bubble up through inert markup? This would completely avoid the <code>mouseover</code> vs <code>mouseenter</code> problem. What is the point of putting a resize cursor on something that is dead without JavaScript? Pointer events shouldn't fire on inert children, and inert parents shouldn't care about interactive children. It's about boundaries, not hierarchy.</p>

</div></div>

<div class="g8"><div class="pad">

<p>Things like SVG are better used as image tags instead of embedded trees, just slotting into place while ignoring their surroundings. They do need their own tree structure, but there is little reason to graft it onto HTML/CSS, inheriting original sin. The nodes have too little in common. At most you can share the models, not the controllers.</p>

<p>We should be able to manipulate them from the outside, like a <code>&lt;canvas&gt;</code>, but define and load them declaratively, like an image tag.</p>

<p>For that matter, MathML should really be a single inline text tag, optimized for math, not a bunch of tags. Regular text spans are not just "plain text". They are trimmed, joined, bidirectionalized, word wrapped and ellipsified before display. It's a separate embedded layout model that makes up the true, invisible <code>&lt;p&gt;</code> tag. A tag that HTML1 actually sort of got right: as an <em>operator</em>.</p>

<p>We create JavaScript with code, not as <em>abstract syntax trees</em>. Why should I build articles and embedded languages out of enormously nested trees, instead of just typing them out and adding some <em>anchor</em> tags around specific interesting parts? The DOM already inserts invisible text nodes everywhere. We didn't need to wrap all our words in <code>&lt;text&gt;</code> tags by hand just to embiggen one of them. The mutant tree on the right could just look like this:</p>
  
<pre><code class="language-html" tabindex="5">&lt;math&gt;x = (-b &amp;pm; &amp;Sqrt;(b^2 - 4 a c)) / 2a&lt;/math&gt;</code></pre>
<div class="c"></div>

<pre><code class="language-html" tabindex="6">&lt;math&gt;x = (-b &pm; &Sqrt;(b^2 - 4 a c)) / 2a&lt;/math&gt;</code></pre>
<div class="c"></div>

<p class="tc"><em>Wasn't HTML5 supposed to match how people write it? LaTeX exists.</em></p>

<p>And which is easier: defining a hairy new category of pseudo-elements like <code>:first-letter</code> and <code>:first-line</code>… or just telling people to wrap their first letter in a span if they really want to make it giant? It was ridiculous to have this feature in a spec that didn't include tables.</p>

<p>The <code>:first-line</code> problem should be solved differently: you define two <em>separate</em> blocks inside a directive, to spread markup across two children with a content binding. It's no different from flowing text across lines and columns.</p>

</div></div>

<div class="g4 mt1"><div class="pad">

  <pre><code class="language-html" tabindex="7">&lt;mrow&gt;
  &lt;mi&gt;x&lt;/mi&gt;
  &lt;mo&gt;=&lt;/mo&gt;
  &lt;mfrac&gt;
    &lt;mrow&gt;
      &lt;mrow&gt;
        &lt;mo&gt;-&lt;/mo&gt;
        &lt;mi&gt;b&lt;/mi&gt;
      &lt;/mrow&gt;
      &lt;mo&gt;&amp;#xB1;&lt;!--PLUS-MINUS SIGN--&gt;&lt;/mo&gt;
      &lt;msqrt&gt;
        &lt;mrow&gt;
          &lt;msup&gt;
            &lt;mi&gt;b&lt;/mi&gt;
            &lt;mn&gt;2&lt;/mn&gt;
          &lt;/msup&gt;
          &lt;mo&gt;-&lt;/mo&gt;
          &lt;mrow&gt;
            &lt;mn&gt;4&lt;/mn&gt;
            &lt;mo&gt;&amp;#x2062;&lt;!--INVISIBLE TIMES--&gt;&lt;/mo&gt;
            &lt;mi&gt;a&lt;/mi&gt;
            &lt;mo&gt;&amp;#x2062;&lt;!--INVISIBLE TIMES--&gt;&lt;/mo&gt;
            &lt;mi&gt;c&lt;/mi&gt;
          &lt;/mrow&gt;
        &lt;/mrow&gt;
      &lt;/msqrt&gt;
    &lt;/mrow&gt;
    &lt;mrow&gt;
      &lt;mn&gt;2&lt;/mn&gt;
      &lt;mo&gt;&amp;#x2062;&lt;!--INVISIBLE TIMES--&gt;&lt;/mo&gt;
      &lt;mi&gt;a&lt;/mi&gt;
    &lt;/mrow&gt;
  &lt;/mfrac&gt;
&lt;/mrow&gt;
</code></pre>
<div class="c"></div>

<p class="tc"><em>This is the first example in the MathML spec. Really. "Invisible times".</em></p>
</div></div>

<div class="g6 i3"><div class="pad">

<pre><code class="language-html" tabindex="8">&lt;join&gt;
  &lt;box class="first-line"&gt;&lt;/box&gt;
  &lt;box&gt;&lt;/box&gt;
  &lt;content&gt;Hello New World&lt;/content&gt;
&lt;/join&gt;
</code></pre>
<div class="c"></div>

<p class="tc"><em>Would this really be insane?</em></p>

</div></div>

<div class="g8 i2"><div class="pad">

<h2>The Boxed Model</h2>

<p>CSS got it wrong and we're now suffering the consequences. The HTML feature that was ignored in CSS 1 was the thing they should've focused on: tables, which were directives that generated layout. It set us on a path of trying to fake them by piggybacking on supposedly semantic elements, like lipstick on a div. Really we were pigeonholing non-linear layout as a nested styling problem.</p>

<p>Semantic content was a false spectre on the document level. Making our menus out of <code>&lt;ul&gt;</code> and <code>&lt;li&gt;</code> tags did not help impaired users skip to the main article. Adding roman numerals for lists did not help us number our headers and chapters automatically.</p>

<p>View and render trees are supposed to be simple and transparent data structures, the <em>model for</em> and <em>result of</em> layout. This is why absolute positioning is a performance win for mobile: it avoids creating invisible dynamic constraints between things that rarely change. Styles are orthogonal to that, they merely define the shape, not where it goes.</p>

<p>Flash had its flaws, but it worked 15 years ago. Shoving raw SVG or MathML into the DOM—or god forbid XML3D–is a terrible idea. It's like there's an entire class of developers who've now forgotten how fast computers actually are and how memory is supposed to work. A <a href="http://c2.com/cgi/wiki?StringlyTyped">stringly typed</a> kitchen sink is not it.</p>

<p>So I frown when I see people excited about SVG in the browser in the year 2014, making polygons out of CSS 3D or driving divs with React. Yes I know, it's fun and it does work. And Angular shows the web component approach has merit. But we need a way out.</p>

<p>CSS should be limited to style and typography. We can define a real layout system next to it rather than on top of it. The two can combine in something that still includes semantic HTML fragments, but wraps layout as a first class citizen. We shouldn't be afraid to embrace a modular web page made of isolated sections, connected by reference instead of hierarchy.</p>

<p>Not my problem though, I can make better SVGs with WebGL in the meantime. But one can dream.</p>

</div></div>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[On Asm.js]]></title>
    <link href="https://acko.net/blog/on-asmjs/"/>
    <updated>2013-11-27T00:00:00+01:00</updated>
    <id>https://acko.net/blog/on-asmjs</id>
    <content type="html"><![CDATA[<div class="g8 i2"><div class="pad">

<h2 class="sub">Ending The Ice Age of JavaScript</h2>

<p>The demo is striking: Unreal Engine, <a href="https://blog.mozilla.org/futurereleases/2013/05/02/epic-citadel-demo-shows-the-power-of-the-web-as-a-platform-for-gaming/">running live in a browser</a>, powered by Mozilla's <em>Asm.js</em>, offering near native performance with hardware accelerated graphics. A gasp here, a wow there, everyone is surely impressed. And look ma', no plug-ins: just JavaScript, Web Audio and WebGL! Just don't mind the 10 second hang on load as it's <em>Preparing JavaScript</em>.</p>

<p>When I heard of it, it sounded great: a highly optimizable subset of JS, that can be dropped seamlessly into any existing code. It reminded me of Winamp's charming <a href="https://github.com/xbmc/xbmc-rbp/tree/master/xbmc/visualizations/Milkdrop/vis_milkdrop/evallib">evallib</a> <a href="https://github.com/xbmc/xbmc-rbp/blob/master/xbmc/visualizations/Milkdrop/vis_milkdrop/evallib/Compiler.c">compiler</a>, used in <a href="/blog/avs">AVS</a> and <a href="https://github.com/jberg/butterchurn">Milkdrop</a>, which only did floating point expressions. It spawned a whole subculture of visual hackery based on little more than a dozen or so math functions and some clever graphics routines. It showed the power of being able to turn scripts into optimal machine code on the fly, and having a multimedia platform at your disposal while doing so.</p>

</div></div>

<div class="c"></div>

<div class="wide mt1">
    <a target="_blank" href="http://www.unrealengine.com/html5/"><img src="https://acko.net/files/asmjs/epiccitadel.jpg" alt="Epic Citadel" /></a>
</div>

<div class="g8 i2 mt1"><div class="pad">

<p>But that's not what Asm.js is for at all. It's not for people, it's a compiler target, a way of converting non-JS code into a form that browsers handle well. Its design is based on how JavaScript handles numbers: as 64-bit <em>doubles</em>, onto which you can perform select 32-bit integer operations. As such, Asm.js seems like a sweet hack similar to UTF-8, an elegant way of encoding something complicated under strong legacy constraints: typed arithmetic despite a single number type. Yet the part of me that remembers pushing pixels with MMX, that watched this web thing for more than a few years, can't help but ask exactly what it is we're trying to do here.</p>

<p>Asm.js deserves closer inspection for two reasons. First, it's the one "native browser VM" that doesn't massively reinvent wheels. Second, it's the only time a browser vendor's "next-gen JS" attempts have actually gotten everybody else to <a href="https://blog.mozilla.org/futurereleases/2013/11/26/chrome-and-opera-optimize-for-mozilla-pioneered-asm-js/">pay attention</a>. But what are we transitioning into exactly?</p>

<h2>LLVM to Asm</h2>

<p>To understand Asm.js, you have to understand <a href="http://llvm.org">LLVM</a>. Contrary to its name, it's not really a Virtual Machine but rather a compiler. More precisely, it's the modular core of a compiler, along with assorted tools and libraries. You plug in different front-ends to parse different languages (e.g. C or C++), and different back-ends to target different machines (e.g. Intel or ARM). LLVM can do common tasks like optimization in a platform and language agnostic way, and as such should probably be considered an open-source treasure.</p>

<p class="tc"><a href="http://www.aosabook.org/en/llvm.html"><img src="https://acko.net/files/asmjs/llvm.png" alt="llvm" /></a></p>

<p class="tc"><em>LLVM architecture</em> (<a href="http://www.aosabook.org/en/llvm.html">source</a>)</p>

<p>Per <a href="http://www.codinghorror.com/blog/2007/07/the-principle-of-least-power.html">Atwood's law</a>, it was inevitable that someone decided the back-end should be JavaScript. Thus was born <a href="https://github.com/kripken/emscripten">emscripten</a>, turning C into JS—or indeed, <em>anything</em> native into JS. Because the output is tailored to how JS VMs work, this already gets you pretty far. The trick is that native code manages its own memory, creating a <a href="http://stackoverflow.com/questions/79923/what-and-where-are-the-stack-and-heap">stack and heap</a>. Hence you can output JS that just manipulates pre-allocated typed arrays as much as possible, and minimizes use of the garbage collector.</p>

<p>This works particularly well because JavaScript VMs already cheat when it comes to numbers and number types. They receive special treatment compared to other data. You can find a good overview in <a href="http://wingolog.org/archives/2011/05/18/value-representation-in-javascript-implementations">value representation in JavaScript implementations</a>. The gist is that JS VMs handle floating point and integer arithmetic separately and efficiently, with lots of low level trickery to speed up computation. Modern VMs will furthermore try to identify code that uses one type only, and emit highly optimized type-specific code with only minimal checks at the boundaries. It's this kind of code that emscripten can emit a lot of, e.g. translating the clang compiler into <a href="http://kripken.github.io/clangor/clang.js">48MB of JS</a>.</p>

</div></div>

<aside class="g5 c">
<p class="codeblock">
<code>function DiagModule(stdlib, foreign, heap) {
  "use asm";

  // Variable Declarations
  var sqrt = stdlib.Math.sqrt;

  // Function Declarations
  function square(x) {
      x = +x;
      return +(x*x);
  }

  function diag(x, y) {
      x = +x;
      y = +y;
      return +sqrt(square(x) + square(y));
  }

  return { diag: diag };
}
</code>
</p><p>Example Asm.js code from the spec.</p>
</aside>

<div class="g7"><div class="pad">

<p>Which brings us to the bittersweet hack of Asm.js. Once you realize that C can run 'well' as JavaScript even when the VM has to guess and juggle, imagine how much faster it could be when the VM is in on it.</p>

<p>What this means in practice is a directive "use asm" in a block of tailored code, along with implicit type annotations for arguments, casts and return values. Type casts are <code>x|0</code> for <code>int</code>, <code>+x</code> for <code>double</code>. These annotations are parsed and validated, and optimized code is emitted for the entire code block. This doesn't look bad at all. However, it also looks nothing like real Asm.js code in the wild.</p>

<p>Benchmarks show roughly a 1–2× slowdown compared to native code, significantly faster than normal JS. Hooray, JavaScript wins again, the web is awesome. Because of LLVM, an enormous piece of <em>external, non-web infrastructure</em>. Wait what?</p>

</div></div>

<div class="g8 i2"><div class="pad">

<h2>Impostor Syndrome</h2>

<p>When something bugs me that I can't put my finger on, there's usually a contradiction that I'm not seeing. After a few talks, articles and conversations, it seems pretty obvious: it puts JavaScript on a pedestal, even as it makes it irrelevant.</p>

<p>It makes JavaScript irrelevant because with LLVM's infrastructure or similar tools, practically anything can or will be compiled into JS.</p>

<p>But it also makes JavaScript more important, focusing the future optimization efforts of browser makers onto it, despite its ill-suited semantics.</p>

<p>It means JavaScript has nothing to do with it, it's just the poison we ended up with, the bitter pill we supposedly have to swallow. So when Brendan Eich <a href="http://www.youtube.com/watch?v=O83-d0t0Ldw">says with a smile</a> "Always bet on JavaScript", what he really means is "Always bet on legacy code" or perhaps "Always bet on politics". When you think about it, it's weird to tell JavaScript developers about Asm.js. It's not actually aimed at them.</p>
  
<p>Looking around, in the browser there's CoffeeScript, TypeScript and Dart. Outside, there's Python, Ruby, Go and Rust. Even CSS now has offspring. The future of the web is definitely multilingual and some people want to jump ship, they're just not sure which one will actually sail yet.</p>

<p>When faced with a legacy mechanism like UTF-8 or indeed Asm.js, we have to ask, is it actually necessary? In the case of UTF-8, it's a resounding yes: we need to assign unique names to things, and this name has to work with both modern and legacy software, passing through unharmed as much as possible. UTF-8 solves a bunch of problems while causing very few.</p>

<p>But with Asm.js, it's just a nice to have. All Asm.js code is new, there is no vault of legacy code that will stop working if we do it wrong. We can already generate functioning JS for legacy browsers, along with something new for alternative VMs. Having one .js file that does both is merely a convenience, and a dubious one at that.</p>

<p>Indeed, the unique appeal of Asm.js is for the browser maker who implements it first: it lets their JS VM race closer to that much desired Native line. It also turns any demo that uses Asm.js into an instant benchmark, which other vendors have to catch up with. It's a rational choice made in self interest, but also a tragedy of the commons.</p>

<p>Maybe that's a bit hyperbolic, but work with me here. There's a serious amount of defeatism and learned helplessness at work here, and again a contradiction. We seek to get ever closer to native performance, yet fall short by design, resigning ourselves to never quite reaching it. I can't be the only one who finds that completely bizarre, when there's laptops and phones running entirely on a web stack now?</p>

<p>If you look at the <a href="https://wiki.mozilla.org/Javascript:SpiderMonkey:OdinMonkey">possible future of Asm.js</a>, there's SIMD extensions, long ints with value objects, math extensions, support for specific JVM/CLR instructions and more. Asm.js is positioned not just as something that works today, but that leads into a bright future to boot. And yet, it all has to be shoehorned into something that is still 100% JavaScript, even as that target itself consists of moving goalposts.</p>

</div></div>

<div class="c"></div>

<div class="wide mt1">
  <p class="mt0"><img src="https://acko.net/files/asmjs/code.jpg" alt="Epic Citadel Code" /></p>
  <p class="tc"><em>Part of Unreal Engine, JSified.</em></p>
</div>

<div class="g8 i2"><div class="pad">

<h2>History Repeating</h2>

<p>So fast forward a year or two. Firefox has completed its wishlist and Asm.js has filled its instruction set gaps. Meanwhile Chrome has continued to optimize V8. Will they have the same new language features that Firefox has? Will they officially support Asm.js? Or push Dart and PNaCl, expanding their influence through ChromeOS and Android? Your guess is as good as mine. As for IE and Safari, I'll just pencil in "behind" for now and leave it at that.</p>

<p>But a certain phrase comes to mind: embrace and extend. From multiple fronts.</p>

</div></div>

<aside class="g4"><div class="pad">
  
  <p><img src="https://acko.net/files/asmjs/asmjsinside.png" alt="asm.js inside" /></p>

</div></aside>

<div class="g8"><div class="pad">

<p>It looks like a future where your best bet to get things running fast in a browser is to do decidedly non-web things. You compile something like C to the different flavors of web at your disposal, either papering over their strengths, or tailoring for each individually. That's not something I personally look forward to, as much as it might arouse Epic's executives and shareholders today.</p>

<p>Web developers wouldn't actually be working that differently. They might be using a multi-language framework like Angular, or dropping in a neat C physics library somebody cross compiled for them. I doubt they'll have a nice web-native way to run the same performance-critical code everywhere. You'll just waste some battery life because a computer pretended to be a JavaScript developer. For backwards compatibility with browsers that auto-update every few weeks. Eh?</p>

</div></div>

<div class="g8 i2"><div class="pad">

<p>I admit, I don't know what the post-JS low level future should look like either, but it should probably be closer to <a href="http://mbebenita.github.io/LLJS/">LLJS</a>'s nicely struct'ed and typed order, than either the featherweight of Asm.js <em>or</em> the monolithic Flash-replacement that is PNaCl.</p>

<p>The big problem with Asm.js isn't that it runs off script rather than bytecode, it's that the code is generated to match how JS engines work rather than how CPUs compute. At best it will be replaced with something more sensible later or just fizz out as an optimization fad. At worst it'll become the <a href="http://en.wikipedia.org/wiki/IA-32">IA-32</a> of the web, still pretending to be an 8086 if asked to.</p>

<p>Looking ahead, there's computation with WebCL, advanced GLSL shaders and more on the horizon. That's a whole set of problems that can become much simpler when "browser" is a language that everyone can speak, to and from, rather than a weird write-only dialect built on a tower of Babel. We don't just need a compilation target, we need a compilation source, as well as a universal intermediate representation.</p>

<p>And this is really the biggest contradiction of them all. Tons of people have invested countless hours to build these VMs, these new languages, these compilers, these optimizations. Yet somehow, they all seem to agree that it is impossible for them to sit down and define the most basic glue that binds their platforms, and implement a shared baseline for their many innovations.</p>

<p>We really should aim higher than a language frozen after 10 days, thawing slowly over 20 years.</p>

</div></div>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Making MathBox]]></title>
    <link href="https://acko.net/blog/making-mathbox/"/>
    <updated>2012-11-14T00:00:00+01:00</updated>
    <id>https://acko.net/blog/making-mathbox</id>
    <content type="html"><![CDATA[<div class="g8 i2 first"><div class="pad">

  <h2 class="sub">Presentation-Quality Math with Three.js and WebGL</h2>

</div></div>

<div class="c"></div>

<aside class="g4"><div class="pad tc">
  <iframe class="mathbox" src="/files/mathbox/MathBox.js/examples/ProjectiveLine.html?c3d6624d" height="600"></iframe>
  A fun little graph involving rational functions on the <a href="http://en.wikipedia.org/wiki/Real_projective_line">real projective line</a>.
</div></aside>

<div class="g8"><div class="pad">

  <p>For most of my life, I've found math to be a visual experience. My math scores went from crap to great once I started playing with graphics code, found some demoscene tutorials, and realized I could reason about formulas by picturing the graphs they create. I could apply operators by learning how they morph, shift, turn and fold those graphs and create symmetries. I could remember equations and formulas more easily when I could layer on top the visual relationships they embody. I was less likely to make mistakes when I could augment the boring symbolic manipulation with a mental set of visual cross-checks.</p>

  <p>So, when tasked with holding a conference talk on <a href="http://www.youtube.com/watch?v=Zkx1aKv2z8o">how to make things out of math</a> at <a href="http://www.full-frontal.org">Full Frontal</a>—later redone at <a href="http://www.webdirections.org/">Web Directions Code</a>—I knew the resulting presentation would have to consist of intricate visualizations as the main draw, with whatever I had to say as mere glue to hold it together.</p>

  <p>The problem was, I didn't know of a good tool to do so, and creating animations by hand would probably be too time consuming. With the writings of <a href="http://www.maa.org/devlin/LockhartsLament.pdf">Paul Lockhart</a> and <a href="http://worrydream.com/KillMath/">Bret Victor</a> firmly in mind, I also knew I wanted to start blogging more about mathematical concepts in a non-traditional way, showing the principles of calculus, analysis and algebra the way I learnt to see them in my head, rather than through the obscure symbols served up in engineering school.</p>

  <p>So I set out to create that tool, keeping in mind the most important lesson I've picked up as a web developer: one cannot overstate the value in being able to send someone a link and have it just work, right there. It was obvious it would have to be browser-based.</p>

  <p><em>2015 Update: MathBox has evolved, <a href="/blog/mathbox2">version 2 is now available</a></em>.</p>

</div></div>

<aside class="g4 i2 c"><div class="pad">
  <p class="tc">
    <a href="http://www.youtube.com/watch?v=Zkx1aKv2z8o">
    <img style="top: 0" src="/files/fullfrontal/video2.jpg" alt="Video" />
    Presentation Video<br />(updated)
    </a>
  </p>
</div></aside>

<aside class="g4"><div class="pad">
  <p class="tc">
    <a href="http://acko.net/files/fullfrontal/fullfrontal/wdcode/online.html">
    <img style="top: 0; -webkit-box-shadow: 0 2px 10px rgba(0, 0, 0, .3); -moz-box-shadow: 0 2px 10px rgba(0, 0, 0, .3); box-shadow: 0 2px 10px rgba(0, 0, 0, .3);" src="/files/fullfrontal/slides.png" alt="Slides" />
    Slide Deck<br />
    (updated)
    </a>
  </p>
</div></aside>

<div class="g8 i2 c"><div class="pad">

  <h2>Choose your Poison</h2>

  <p>Now, when people think of graphs in a browser, the natural thought is vector graphics and SVG, which quickly leads to <a href="http://d3js.org">visualization powerhouse d3.js</a>. It really is an amazing piece of tech with a vast library of useful code to accompany it. When I wrapped my head around how d3's enter/exit selections are implemented and how little it actually does to achieve so much, I was blown away. It's just so elegant and simple.</p>

  <p>Unfortunately, d3's core is intricately tied to the DOM through SVG and CSS. And that means ironically that d3 is not really capable of 3D. Additionally, d3 is a power tool that makes no assumptions: it is up to you to choose which visual elements and techniques to use to make your diagrams, and as such it is more like assembly language for graphs than a drop-in tool. These two were show stoppers.</p>

  <p>For one, manually designing layouts, grids, axes, etc. every time is tedious. You should be able to drop in a mathematical expression with as little fanfare as possible and have it come out looking right. This includes sane defaults for transitions and animations.</p>

</div></div>

<div class="g12">
  <iframe class="mathbox" src="/files/mathbox/MathBox.js/examples/Intersections.html?c3d6624d" width="960" height="500"></iframe>
</div>

<div class="g7"><div class="pad">

  <p>For another, I've found that, when in doubt, adding an extra dimension always helps. The moment I finally realized that every implicit graph in N dimensions is really just a slice of an explicit one in N+1 dimensions, a ridiculous amount of things clicked together. And it took until years after studying signal processing to at long last discover the 4D picture of complex exponentiation that tied the entire thing together (projected into 3D below): it revealed the famous "magic formula" involving e, i and π to be a meaningless symbological distraction, a pinhole view of a much larger, much more beautiful structure, underpinning every Fourier and Z transform I'd ever encountered.</p>

</div></div>

<aside class="g5 m1"><div class="pad">
  <p class="tc"><big>
    e<sup>iπ</sup> = -1
  </big></p>
  <p class="tc">This particular formula is not that important.</p>

  <p class="tc"><big>
    e<sup>x+iy</sup> = e<sup>x</sup> &middot; e<sup>iy</sup> = e<sup>x</sup> ∠ y
    
  </big></p>
  <p class="tc">This one is (∠ = rotate by).<br />Unfortunately it has a four dimensional graph.</p>
  
</div></aside>

<div class="g12">
  <iframe class="mathbox" src="/files/mathbox/MathBox.js/examples/ComplexExponentiation.html?c3d6624d" width="960" height="500"></iframe>
</div>

<div class="g8 i2"><div class="pad">

  <p>So, WebGL it was, because I needed 3D. Unfortunately that meant the promise of having it just work everywhere was tempered by a lack of browser support, but I would certainly hope that's something we can overcome sooner than later. Dear Apple and Microsoft: get your shit together already. Dear Firefox and Opera: your WebGL performance could be a lot better.</p>

  <h2>Shady Dealings</h2>

  <p>These days I don't really touch WebGL without going through <a href="http://mrdoob.github.com/three.js/">Three.js</a> first. Three.js is a wonderful, mature engine that contains tons of useful high-level components. At the same time, it also does a great job in just handling the boilerplate of WebGL while not getting in the way of doing some heavy lifting yourself.</p>

  <p>Rendering vector-style graphics with WebGL is not hard, certainly easier than photorealistic 3D. Primitives like lines and points are sized in absolute pixels by default, and with hardware multisampling for anti-aliasing, you get somewhat decent image quality out of it. Though, as is typical for a Web API, we're treated like children and can only cross our fingers and <em>request</em> anti-aliasing politely, hoping it will be available. Meanwhile native developers <a href="http://www.nvidia.com/object/coverage-sampled-aa.html">have full control</a> over speed and quality and can adjust their strategy to the specific hardware's capabilities. The more things change... And then <a href="http://code.google.com/p/chromium/issues/detail?id=159275">Chrome decided to disable anti-aliasing altogether</a> due to esoteric security issues with buggy drivers. Bah.</p>

  <p>Now, when rendering with WebGL, you really have two options. One is to just treat it as a dumb output layer, loading or generating all your geometry in JavaScript and rendering it directly in 3D. With the speed of JS engines today, this can get you pretty far.</p>

</div></div>

<div class="g7"><div class="pad">
  <p>The second option is to leverage the GPU's own capabilities as much as possible, doing computations in GLSL through so-called vertex and fragment shader programs. These are run for every vertex in a mesh, every pixel being drawn, and have been the main force driving innovation in real-time graphics for the past decade. With the goal of butter-smooth 60fps graphical goodness, this seemed like the better choice.</p>

  <p>Unfortunately, GLSL shaders are rather monolithic things. While you do have the ability to create subroutines, every shader still has to be a stand-alone program with its own main() function. This means you either need to include a shader for every possible combination of operations, or generate shader code dynamically by concatenating pre-made snippets or using #ifdef switches to knock them out. This is the approach taken by Three.js, which results in some <a href="https://github.com/mrdoob/three.js/blob/master/src/renderers/WebGLShaders.js">very hairy code</a> that is neither easy to read nor easy to maintain.</p>

  <p>Having made a prototype, I knew I wanted to show continuous transitions between various coordinate systems (e.g. polar and spherical), knew I needed to render shaded and unshaded geometry, and knew I would need to slot in specific snippets for things like point sprites, bezier curves/surfaces, dynamic tick marks, and more. Sorting this all out Three.js-style would be a nightmare.</p>

</div></div>

<aside class="g5">
<p class="codeblock">
<code>uniform sampler2D texture;
varying vec2 vUV;

void main() {
  gl_FragColor = texture2D(texture, vUV);
}
</code></p>
<p>
A pixel or fragment shader that looks up a pixel's color in a texture.
</p>
<p class="codeblock">
<code>uniform mat4 projectionMatrix;
uniform mat4 modelViewMatrix;
attribute vec4 position;
attribute vec2 uv;
varying vec2 vUV;

void main() {
  vUV = uv;

  gl_Position = projectionMatrix
              * modelViewMatrix
              * position;
}
</code>
</p>
<p>
A vertex shader that projects a 3D position into 2D by applying two matrices. It also provides UV coordinates for the texture look up.
</p>
</aside>

<aside class="g4 c">
<p style="top: 0;"><img src="/files/mathbox/shadergraph.png" style="width: 93%" /></p>
<p class="codeblock">
<code>var graph =
  factory
    .snippet('split')
    .group()
      .snippet('top')
    .next()
      .snippet('middle')
    .next()
      .snippet('bottom')
    .combine()
    .snippet('join')
    .end();
</code></p>
<p>
ShaderGraph's factory API lets you build shader chains with very little hassle. In this case, the names refer to IDs of &lt;<strong>script</strong>&gt; tags in the source.
</p>
</aside>

<div class="g8"><div class="pad">

  <p>So I wrote a library to solve that problem, called <a href="https://github.com/unconed/ShaderGraph.js">ShaderGraph.js</a>. It is best described as a smart code-concatenator, a few steps short of writing a full blown compiler. You feed it snippets of GLSL code, each with one or more inputs and outputs, and these get parsed and turned into lego-like building blocks. Each input/output becomes an outlet, and outlets are wired up in a typical dataflow style. Given a graph of connected snippets, it can be compiled back into a program by assembling the subroutines, assigning intermediate variables and constructing an appropriate main() function to invoke them. It also exports a list of all external variables, i.e. GLSL uniforms and attributes, so you can control the program's behavior easily.</p>

  <p>If I'd stopped there however, I'd have just replaced the act of manual code writing with that of manually wiring graphs. So I applied the principle of convention-over-configuration instead: you tell ShaderGraph to connect two snippets, and it will automatically match up outlets by name and type. This is augmented by a chainable factory API, which allows you to pass a partially built graph around. It allows different classes to work together to build shaders, each inserting their own snippets into the processing chain.</p>
  
<p>For example, to render a Bezier surface, the vertex shader is composed of: cubic interpolation, viewport transform (position + tangents), normal calculation and lighting. When transforming to e.g. a polar viewport, the surface normals are seamlessly recalculated. It really works like magic and I can't wait to use this in my next WebGL projects.</p>

</div></div>

<div class="g8 i2"><div class="pad">
  
  <h2>Viewports, Primitives and Renderables</h2>

  <p>At its core, Three.js matches pretty directly with WebGL. You can insert objects such as a Mesh, Line or ParticleSystem into your scene, which invokes a specific GL drawing command with high efficiency. As such, I certainly didn't want to reinvent the wheel.</p>

  <p>Hence, MathBox is set up as a sort of scene-manager-within-a-scene-manager. It's a little sandbox that speaks the language of math, allowing you to insert various <em>primitives</em> like curves, vectors, axes and grids. Each of these primitives then instantiates one or more <em>renderables</em>, which simply wrap a native Three.js object and its associated ShaderGraph material. Thus, once instantiated, MathBox gets out of the way and Three.js does the heavy lifting as normal. You can even insert multiple mathboxen into a Three.js scene if you like, mixed in with other objects.</p>

<p><img src="/files/mathbox/MathBox.js/resources/architecture.png" alt="MathBox Architecture" class="squeeze" style="margin-left: -10px;" /></p>

  <p>For example, a vector primitive is rendered as an arrow: it consists of a shaft and an arrowhead, realized as a line segment and a cone. An axis primitive is an arrow as well, but it also has tick marks (specially transformed line segments), and is positioned implicitly just by specifying the axis' direction rather than a start and end point.</p>

  <p>To render curves and surfaces, you can either specify an array of data points or a live expression to be evaluated at every point. This turned out to be essential for the kinds of intricate visualizations I wanted to show, my slides being driven by timed clocks, shared arrays of data points, and live formulas and interpolations. I even fed in data from a physics engine, and it worked perfectly.</p>

  <p>This is all tied together through Viewport objects, which define a specific mapping from a mathematical coordinate space into the 3D world space of Three.js. For example, the default cartesian viewport has the range [–1, 1] in the X, Y and Z directions. Altering the viewport's extents will shift and scale anything rendered within, as well as reflow grids and tick marks on each axis.</p>

  <p>There are two more sophisticated viewport types, polar and spherical, which each apply the relevant coordinate transform, and can transition smoothly to and from cartesian. More viewport types can be added, all that is required is to define an appropriate transformation in JavaScript and GLSL. That said, defining a seamless transition to and from cartesian space is not always easy, particularly if you want to preserve the aspect-ratio through the entire process.</p>

  <h2>Interpolate all the things!</h2>

  <p>Finally, I had to tackle the problem of animation, keeping in mind a tip I learnt from the <a href="http://www.youtube.com/watch?v=4gZ5rsAHMl4">ever so mindbending Vihart</a>: "If I can draw the point of a sentence, I don't actually need to say the sentence." This applies doubly so for animation: every time you replace a "before" and "after" with a smooth transition, your audience implicitly understands the change rather than having to go look for it.</p>

  <p>Hence, each primitive can be fully animated. Each has a set of options (controlling behavior) and styles (controlling GLSL shaders), and there is a universal animator that can interpolate between arbitrary data types in a smart fashion.</p>

  <p>For example, given a viewport with the XYZ range [[–1, 1], [–1, 1], [–1, 1]], you can tell it to animate to [[0, 2], [0, 1], [–3, 3]], and it just works. The animator will recursively animate each subarray's elements, and any dependent objects like grids and axes will reflow to match the intermediate values. This works for colors, vectors and matrices too. In case of live curves with custom expressions, the animator will invoke both the old and the new, and interpolate between the results.</p>

</div></div>

<div class="g8 i2">
  <div class="slideshow compact" data-skip="1">
    <div class="iframe">
      <iframe class="mathbox paged" src="/files/mathbox/MathBox.js/examples/BezierSurface.html?c3d6624d" width="640" height="400"></iframe>
    </div>
    <div class="steps">
      <div class="step"></div>
      <div class="step"></div>
      <div class="step"></div>
      <div class="step"></div>
      <div class="step"></div>
      <div class="step"></div>
      <div class="step"></div>
    </div>
  </div>
</div>

<div class="g8 i2"><div class="pad">
  <p>However, executing animations manually in code is tedious, particularly in a presentation, where you want to be able to step forward and backward. So I added a Director class whose job it is to coordinate things. All you do is feed it a script of steps (add this object, animate that object). Then, as it applies them, it remembers the previous state of each object and generates an automatic rollback script. It also contains logic to detect rapid navigation, and will hurry up animations appropriately. This avoids that agonizing situation of watching someone skip through their slide deck, playing the same cheesy PowerPoint transitions over and over again.</p>

  <h2>Presenting Naturally</h2>
  
  <p>With MathBox's core working, it was time to build my slides for the conference. After a quick survey, I quickly settled on <a href="http://imakewebthings.com/deck.js/">deck.js</a> as an HTML5 slidedeck solution that was clean and flexible enough for my purposes. However, while MathBox can be spawned inside any DOM element, it wouldn't work to insert a dozen live WebGL canvases into the presentation. The entire thing would grind to a halt or at least become very choppy.</p>
  
  <p>So instead, I integrated each MathBox graphic as an IFRAME, and added some logic that only loads each IFRAME one slide before it's needed, and unloads it one slide after it's gone off screen. To sync up with the main presentation, all deck.js navigation events were forwarded into each active IFRAME using <em>window.postMessage</em>. With the MathBox Director running inside, this was very easy to do, and meant that I could skip around freely during the talk, without any worries of desynchronization between MathBox and the associated HTML5 overlays.</p>
  
  <p>In fact, I applied a similar principle to this post. To avoid rendering all diagrams simultaneously and spinning up laptop fans more than necessary, each MathBox IFRAME is started as it scrolls into view and stopped once it's gone.</p>
  
  <p>I've also found that having a handheld clicker makes a huge difference while speaking—as it allows you to gesture freely and move around. So, I grabbed the infrared remote code from VLC and built a <a href="https://github.com/unconed/iremotepipe/">simple bridge</a> from to Cocoa to Node.js to WebSocket to allow the remote to work in a browser. It's a shame Apple's decided to discontinue IR ports on their laptops. I guess I'll have to come up with a BlueTooth-based solution when I upgrade my hardware.</p>

  <h2>Towards MathBox 1.0</h2>

  <p>In its current state, MathBox is still a bit rough. The selection of primitives and viewports is limited, and only includes the ones I needed for my presentation. That said, it is obvious you can already do quite a lot with it, and I couldn't have been happier to hear that all this effort had the desired response at the conference. I wasn't 100% sure whether other people would have the same a-ha moments that I've had, but I'm convinced more than ever that seeing math in motion is essential for honing our intuition about it. MathBox not only makes animated diagrams much easier to make and share, but it also opens the door to making them interactive in the future.</p>

  <p>I plan to continue to evolve MathBox as needed by using it on this site and addressing gaps that come up, though I've already identified a couple of sore points:</p>

  <ul>
  <li><span class="strike"><span>I used tQuery as a boilerplate and because I liked the idea of having a chainable API for this. However, this also means it's currently running off an outdated version of Three.js. I need to look into updating and/or dropping tQuery.</span></span><br />MathBox has been updated to Three.js r53.</li>
  <li><span class="strike"><span>Numeric or text labels are completely unsupported. It should be possible to use my CSS3D renderer for Three.js to layer on beautifully typeset <a href="http://www.mathjax.org">MathJax</a> formulas, positioning them correctly in 3D on top of the WebGL render.</span></span><br />I've added labeling for axes. I've integrated MathJax, but it's tricky because the typesetting is painfully slow in the middle of a 60fps render. But it's automatically used if MathJax is present.</li>
  <li>All styles have to be specified on a per-object basis. Some form of stylesheet, default styles or class mechanism to allow re-use seems like an obvious next step.</li>
  <li>There are undoubtedly memory leaks, as I was focused first and foremost on getting it to work.</li>
  <li>Expressions that don't change frame-to-frame are still continuously re-evaluated, which is wasteful. There is a <code>live: false</code> flag you can set on objects, but it triggers a few bugs here and there.</li>
  <li><span class="strike"><span>There needs to be a predictable, built-in way of running a clock per slide to sync custom expressions off of. In my presentation I used a hack of clocks that start once first invoked, but this lacks repeatability.</span></span><br />I added a <code>director.clock()</code> method that gives you a clock per slide.</li> 
  </ul>

  <p>Finally, it doesn't take much imagination to imagine a MathBox Editor that would allow you to build diagrams visually rather than having to use code like I did. However, that's a can of worms I'm not going to open by myself, especially because the API is already quite straightforward to use, and the library itself is still a bit in flux. Perhaps this could be done as an extension of the <a href="http://mrdoob.github.com/three.js/editor/">Three.js editor</a>.</p>

  <p>You can see what MathBox is really capable of in the <a href="http://www.youtube.com/watch?v=Zkx1aKv2z8o">conference video</a>. I invite you to <a href="https://github.com/unconed/MathBox.js">play around with MathBox</a> and see what you can make it do. Contributions are welcome, and the architecture is modular enough to allow its functionality to grow for quite some time.</p>

</div></div>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Going Full Frontal]]></title>
    <link href="https://acko.net/blog/going-full-frontal/"/>
    <updated>2012-11-11T00:00:00+01:00</updated>
    <id>https://acko.net/blog/going-full-frontal</id>
    <content type="html"><![CDATA[<div class="g8 i2 first"><div class="pad">

  <h2 class="sub">Making things with Maths</h2>

</div></div>

<div class="c"></div>

<aside class="g5">
  <p class="tc">
    <img style="top: 0" src="/files/fullfrontal/venue.jpg" alt="Duke of York's Picturehouse, a one-screen marvel" />
    Duke of York's Picturehouse, a gorgeous venue, and "<em>the oldest continuously operating purpose built cinema in Britain that has retained both its original name and remains largely unaltered.</em>"
  </p>
</aside>

<div class="g7 first"><div class="pad">

<p>Last week, I had the privilege of speaking about "Making things with Maths" at <a href="http://www.full-frontal.org">Full Frontal</a>, a tech conference hosted in a gorgeous picturehouse in the seaside town of Brighton, UK. I was nervous as hell: I hadn't attended a tech conference in ages, let alone taken the stage, and I'd never done a talk on this subject before. I'd been planning and working for months to assemble the code just to be able to show what I saw in my head—and of course scrambled to finish the week before regardless. This talk has been the number one thing on my mind for a while.</p>

<p>Yet two days later, I barely remember my own part in it, and find myself mulling over everything else that was said instead. It was simply too good, too provoking, not to think about.</p>

<p>The lovely duo of organizers, <a href="http://remysharp.com">Remy</a> and <a href="https://twitter.com/Julieanne">Julie Sharp</a>, have crafted something very special. The line up was stellar: each and every speaker challenged my preconceptions with the kind of casualness that only in-depth experience can bring.</p>

</div></div>

<div class="g8 i2"><div class="pad">

<p>Arguments for abandoning the purity of vanilla HTML (<a href="http://2012.full-frontal.org/speaker/james#james">James Pearce</a>) were followed by a philosophical lesson on not throwing away the baby with the bathwater (<a href="http://2012.full-frontal.org/speaker/john#john">John Allsopp</a>) and I found myself agreeing wholeheartedly with both, cognitive dissonance notwithstanding. As someone who isn't a fan of the mobile app world, I had to admit I was ignorant on the difficulties of implementing offline web apps (<a href="http://2012.full-frontal.org/speaker/andrew#andrew">Andrew Betts</a>), and blissfully unaware of the absolute zoo of devices people really do try to access the web with (<a href="http://2012.full-frontal.org/speaker/anna#anna">Anna Debenham</a>), webological purity be damned.</p>

<p>We've barely scratched the surface of what browsers can do (<a href="http://2012.full-frontal.org/speaker/paul#paul">Paul Kinlan</a>), we need to chase the high of writing code and actually have it Just Work (<a href="http://2012.full-frontal.org/speaker/rebecca#rebecca">Rebecca Murphey</a>), and above all, we need to remember where it all came from (<a href="http://2012.full-frontal.org/speaker/chris#chris">Chris Wilson</a>), lest we repeat the mistakes of the past. If you weren't one of the lucky people who managed to snag tickets before it sold out, take some time out of your busy day to enjoy these sessions in video once they are posted online rather than just skipping through slides.</p>

<p>If there's one thing that stood out though, it's how little of what I heard on and off-stage is part of the daily discourse online in the tech world, in the news or on sites like HackerNews, Twitter and Reddit. We only see caricatures of these conversations. More than ever, I'm convinced I need to filter out these echochambers from my thoughts and seek out more substance. Particularly, the Silicon Valley-centric TechCrunch-driven worship of runaway success adds nothing, and only holds us back. It makes people think they need to chase something that only ever happens by accident, and diverts attention away from rolling up your sleeves and doing what actually needs to be done. This was emphasized all the more by the fact that the venue had no wi-fi, which meant everyone had their eyes away from their screens for a change.</p>

</div></div>
<div class="g8"><div class="pad">

<p>To seal the deal, the conference was flanked by in-depth workshops, the obvious drinks and social gatherings and even a <a href="http://nodecopter.com">NodeCopter hackathon</a>, where we made quadcopters do crazy things with nothing more than JavaScript. I only wish I'd been more rested and less jetlagged so I could've spoken to more folks the past few days.</p>

<p>Thank you to the organizers and volunteers, to the event crew, to my fellow speakers, to the people who travelled from near and far to listen, and to whomever decided to stick those funky legs on top of the cinema. They heralded quite literally that things were about to be turned upside down, and the event certainly delivered on that.</p>

<h2>Video and Slides</h2>

</div></div>

<aside class="g4 r">
  <p class="tc">
    <img src="/files/fullfrontal/legs.jpg" />The Legs
  </p>
</aside>

<aside class="g4 c"><div class="pad">
  <p class="tc">
    <a href="http://www.youtube.com/watch?v=ONN3jBly364&amp;list=UUyBAm31tEpZ17hka6ZvVqcg&amp;index=2&amp;feature=plcp">
    <img style="top: 0" src="/files/fullfrontal/video.jpg" alt="Video" />
    Conference Video
    </a>
  </p>
</div></aside>

<aside class="g4"><div class="pad">
  <p class="tc">
    <a href="http://acko.net/files/fullfrontal/fullfrontal/slides-net/">
    <img style="top: 0; -webkit-box-shadow: 0 2px 10px rgba(0, 0, 0, .3); -moz-box-shadow: 0 2px 10px rgba(0, 0, 0, .3); box-shadow: 0 2px 10px rgba(0, 0, 0, .3);" src="/files/fullfrontal/slides.png" alt="Slides" />
    Slide Deck
    </a>
  </p>
</div></aside>

<div class="g8 i2"><div class="pad">

<p><em>You can read more about MathBox in <a href="/blog/making-mathbox">the follow-up blog post</a>.</em></p>
<p><em>The adventurous can go see how the sausage was made and check out <a href="https://github.com/unconed/MathBox.js">the code for MathBox</a>, the library I wrote to make it happen, as well as the <a href="https://github.com/unconed/fullfrontal">HTML5 slide deck</a>.</em></p>

</div></div>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Introducing Facing.me]]></title>
    <link href="https://acko.net/blog/introducing-facing-me/"/>
    <updated>2012-04-25T00:00:00+02:00</updated>
    <id>https://acko.net/blog/introducing-facing-me</id>
    <content type="html"><![CDATA[<div class="g8 i2 first"><div class="pad">

  <h2 class="sub">A unique way to meet people</h2>

</div></div>

<div class="c"></div>

<aside class="g5">
  <p class="tc">
    <img style="top: 0" src="/files/fme/facing.me.face.jpg" alt="Facing.me" />
  </p>
</aside>

<div class="g7"><div class="pad">

<p>
We've been sending out whispers for a while now, but it's finally out: a new web site called <a href="http://facing.me">Facing.me</a>. Coded and designed by <a href="http://mikejholly.com">Michael Holly</a>, <a href="http://rosshj.com/">Ross Howard-Jones</a> and myself, it promises a <em>unique way to meet people online</em>. This would be the point where the obvious question is dropped: wait, what… you built a <em>dating site</em>?</p>

<p>Sort of. Let me explain.</p>

<p>Having spent many years in the web world, we'd all gotten a bit complacent. The web has settled into its comfortable rhythms. Sites and applications can be modelled quickly and coded on your framework of choice. And nowadays, Web 2.0 cred comes baked in: clean URLs, semantic HTML, AJAX, data feeds, APIs, etc. Isn't this what we all wanted?</p>

<p>But the web continues to evolve, and giants are roaming the playground. Sites like Facebook and Twitter hold people's attention with surgical precision, while engines like Google answer your queries with lightning speed. Given that we've all slotted such services into our workflows and indeed lives, it seems only natural that 'indie' developers should keep up. We can't pretend that a 2000-era style web-page-with-ajax-sprinkles is the pinnacle of modern interactive design.</p>

<p>So we set out to try something different.</p>

</div></div>

<div class="img12">
  <a href="http://facing.me"><img src="/files/fme/facing.me.site.jpg" alt="Facing.me website" /></a>
</div>

<!--
<div class="g8 i2 first"><div class="pad">  

</div></div>
-->

<div class="g6"><div class="pad">

<h2>A Guy Walks into a Bar...</h2>

<p>If you've managed to score an invite, the first thing you'll see is the wall of faces that loads and fills the screen. The second thing you'll notice—we hope at least—is the lack of everything else.</p>

<p>The metaphor we kept in mind was the idea of walking into a bar, and looking around. If you see someone you like, you can go up to them and strike up a conversation. So that's exactly what the app lets you do, through video chat. You can pan around to see more people, and just keep going. If you're looking for something specific, you can filter your view with a simple "I'm looking for…" dialog.</p>

<p>As you mouse around, you can see who's online, and flip open their profile. If you want to strike up a video chat, it happens right there too. If the person is online, they'll see your request immediately in a popup and can choose to accept or decline after reviewing your profile. If they're offline, they'll see your request next time they visit.</p>

<p>To avoid missed connections, you can 'like' people you're interested in. You'll see (and hear) a notification pop up the moment they're online. You can keep the app open in a background tab and never miss a thing.</p>

<p>Aside from some minor social glue and a few fun little extras for you to discover, that's it. It's our twist on a <em>minimally viable product</em> if you will. Studies have shown that online matching algorithms are a poor predictor for how well people mesh in person. Until you meet face-to-face, you just don't know. We think direct, spontaneous video chat is a better first step rather than endless profile matching and messaging.</p>

</div></div>

<aside class="g6 m1">
  <p class="p0"><img src="/files/fme/facing.me.start.jpg" alt="Facing.me welcome screen" /></p>

  <p class="p0"><img src="/files/fme/facing.me.profile.jpg" alt="Facing.me welcome screen" /></p>

  <p class="p0"><img src="/files/fme/facing.me.growl.jpg" alt="Facing.me notification" /></p>

  <p class="p0"><img src="/files/fme/facing.me.like.jpg" alt="Facing.me liking" /></p>
</aside>

<div class="g8 i2"><div class="pad">

<h2>Polishing Bacon</h2>

<p>But despite its minimalism, a big aspect of Facing.me is the effort and care we put into it. Our goal was to achieve a level of polish typically reserved for premium iPhone apps and bring it into the browser. We wrapped the whole thing in a crisp design, enhanced with tasteful web fonts. But most importantly, we sought to expose the app's functionality with as little interruption as possible. To do that, we layered on plenty of transitions driven by CSS3 and JavaScript, and stream in data and content as needed.</p>

<p>Based on previous work in custom animations—and <a href="/blog/abusing-jquery-animate-for-fun-and-profit-and-bacon">bacon</a>—we refined the approach of using jQuery as an animation helper for completely custom transitions. We tell jQuery to animate placeholder properties on orphaned proxy divs, and key off those animations with per-frame code to drive the fancy stuff.</p>

</div></div>

<div class="img12">
  <img src="/files/fme/transition.jpg" alt="facing.me animation example" />
</div>

<div class="g8 i2"><div class="pad">
<p>As a result, we can have a photo grow a picture frame as you pick it up, and then flip it around to show a person's full profile. This careful choreography involves animating about a dozen CSS properties, including borders, shadows, margins and 3D transforms, all with custom expressions and hand-tuned animation curves. Similar transitions are used for lightbox dialogs.</p>

<p>Throughout all of this, the animations remain eminently manageable. We can interrupt and reverse them at any point, and run multiple copies at the same time, thanks to pervasive use of view controllers. Far from being a useless tech demo, it actually enables us to craft the user experience exactly the way we like it: being able to acknowledge user intentions with intuitive feedback no matter what's going on, and firing off new events and requests without worrying about the internal state. Gone are the fragile jQuery behavior soups of old.</p>

<p>The one downside is that only the newer browsers—i.e. Chrome, Safari and Firefox—get to see everything the way it was intended. And actually the performance in Firefox is still a bit disappointing. IE9 users will have to be satisfied with a crude 2D approximation until IE10 comes out.</p>

</div></div>

<div class="g8 first"><div class="pad">

<h2>Rapid Rails and Real-Time Node</h2>

<p>To make all this work effectively on the server-side, we used a dual-mode stack of Rails and Node.js.</p>

<p>The Rails side houses the app's models and controllers, and provides an API for all the client-side JavaScript to do its job. Video chats are handled through Flash and routed through its built-in peer-to-peer functionality.</p>

<p>The node.js component acts as a real-time presence daemon which users connect to over socket.io. It's used to drive the status notifications and to coordinate the video chats. We can exchange any sort of notifications between users with a publish-subscribe model, opening up many interesting avenues for future development.</p>

<p>Overall, this approach has worked out great. Rails' ActiveRecord and the stack around it allowed us to build out functionality quickly and with just the right amount of necessary baggage. We made generous use of Ruby Gems to save time while still maintaining full control.</p>

<p>Node.js's event-driven model adds real-time signalling with no hassle. For the few cases where node.js needs to interface with the Rails database directly, we slot in some manual SQL to take care of that. For everything else, Rails and node.js exchange signed data through the browser.</p>

</div></div>

<aside class="g4 m1"><div class="pad">
  <p><img src="/files/fme/nodejs.png" alt="Node.js" /></p>

  <p><img src="/files/fme/rails.jpg" alt="Rails" /></p>
</div></aside>

<div class="g8 i2 first"><div class="pad">

<h2>Come Take it for a Spin</h2>

<p>Finally, we also put our heads together and made a promo video, voiced by the lovely <a href="https://twitter.com/t1nah">Tina Hoang</a>:</p>

</div></div>

<div style="max-width: 854px; width: 100%; margin: 0 auto">

  <iframe src="http://player.vimeo.com/video/41056588?title=0&amp;byline=0&amp;portrait=0" width="854" height="480" frameborder="0" allowfullscreen="allowFullScreen"></iframe>

</div>

<div class="g8 i2"><div class="pad">

<p>Built in our spare time by just 3 guys in a virtual garage, we're pretty proud of the end result. We'd love for you to take it for a spin, so <a href="http://facing.me">head over to facing.me</a> and grab yourself an invite. There's a feedback form built-in, and any suggestions are welcome.</p>

<p>Discuss on <a href="https://plus.google.com/112457107445031703644/posts/efHMJE1Wxx2">Google Plus</a>.</p>

</div></div>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[This is Your Brain on CSS]]></title>
    <link href="https://acko.net/blog/this-is-your-brain-on-css/"/>
    <updated>2012-02-19T00:00:00+01:00</updated>
    <id>https://acko.net/blog/this-is-your-brain-on-css</id>
    <content type="html"><![CDATA[<div style="display: none"><img src="/files/mri/cover.jpg" alt="" /></div>

<div class="g8 i2 first"><div class="pad">

<p>First things first: the CSS 3D renderer used to power <strike>this</strike> <em>the previous</em> site is now <a href="https://github.com/unconed/CSS3D.js">available on GitHub.com</a>. However, it's still limited to only solid lines and planes. It's also limited to WebKit browsers, as Firefox's CSS 3D support just isn't quite there yet.</p>

<p>
  But CSS 3D is not a one trick pony, and as with many things, what you get out of it depends entirely on what you put in. So here's a disembodied head made out of CSS 3D. It consists of nothing more than a bunch of images stacked up against each other, and integrates perfectly with the existing 3D parallax on this site. Click and drag to rotate, or use the slider to look inside.
</p>

<link rel="stylesheet" href="/files/mri/head.css" type="text/css" media="screen" />

<div id="head-3d">
  <div class="head-viewport" style="height: 500px;">
    <div class="CSS3DCamera" data-var="transform">
      <div class="pedestal">
        
      </div>
      <div class="VolumetricView" data-var="phi slice"></div>
    </div>
  </div>
  <div class="Slider" data-var="slice"></div>
</div>

<p>
  Making the basic effect was actually quite easy. I took an MRI from the <a href="http://graphics.stanford.edu/data/voldata/">Stanford Volume Data Archive</a> and wrote a small script to turn it into a sheet of CSS sprites. There's <a href="http://acko.net/files/mri/MRbrain-color.jpg">one file for color</a>, <a href="http://acko.net/files/mri/MRbrain-alpha8.png">one for opacity</a>, totalling about 2.1 MB. Both files are composited into Canvases and placed in slices into the DOM, offset forward or backwards in 3D. Then there's just some minor logic to rotate the slices in 90 degree increments to follow the camera.
</p>

<p>
  But the slices are rendered as is, and the MRI consists of <a href="http://acko.net/files/mri/MRbrain-alpha8.png">boring grayscale data</a>. Luckily, I can precompute any amount of shaders and effects I want and just bake them into the slices. I geeked out by applying fake specular lighting, for that 'fresh meat' look, and volumetric obscurance to enhance the sense of depth on the inside. I changed the palette to gory colors based on local density, giving the impression of flesh and bone knitting itself together. Creepy, but cool.
</p>

<p>
  I wrapped it in a custom widget, using straight up CSS rather than Three.js this time. I've wanted to play with <a href="http://worrydream.com/Tangle/">Tangle.js</a>, so I used that to hook up the camera controls and slider. That's pretty much it. In an ideal world, the jarring transition when rotating would be covered up by a nice transition, but the browsers don't like it.
</p>

</div></div>

<script type="text/javascript">
// <!--
Acko.queue(function () {

    var model = {
      initialize: function () {
        // State
        this.theta = 0.0;
        this.phi = 0.5;
        this.slice = 0;
      },

      update: function () {
        this.transform = 'rotateX('+ -this.theta +'rad) rotateY('+ this.phi +'rad)';
      },
    };

    Tangle.classes.CSS3DCamera = {
      initialize: function (element, options, tangle, variables) {
        this.element = element;

        this.element.style.transformStyle = 'preserve-3d';

        var that = this;
        element.addEventListener('mousedown', function (event) {
          that.drag = true;
          that.dragLast = that.dragOrigin = { x: event.pageX, y: event.pageY };
          event.preventDefault();
        });
        document.addEventListener('mouseup', function (event) {
          that.drag = false;
        });
        document.addEventListener('mousemove', function (event) {
          if (!that.drag) return;
          var total = { x: event.pageX - that.dragOrigin.x, y: event.pageY - that.dragOrigin.y },
              delta = { x: event.pageX - that.dragLast.x, y: event.pageY - that.dragLast.y };
          that.dragLast = { x: event.pageX, y: event.pageY };
          mousemove(that.dragOrigin, total, delta);
        });

        function mousemove(origin, total, delta) {
          var phi = tangle.getValue('phi') + delta.x * .01,
              theta = Math.min(1, Math.max(-.2, tangle.getValue('theta') + delta.y * .01));

          tangle.setValue('phi', phi);
          tangle.setValue('theta', theta);
        }
      },

      update: function (element, value) {
        this.element.style.WebkitTransform = value;
        this.element.style.MozTransform = value;
        this.element.style.transform = value;
      },
    },

    Tangle.classes.Slider = {
      initialize: function (element, options, tangle, variables) {
        var that = this;

        this.tangle = tangle;
        this.element = element;

        this.bar = document.createElement('div');
        this.bar.className = 'bar';
        this.element.appendChild(this.bar);

        this.handle = document.createElement('div');
        this.handle.className = 'handle';
        this.element.appendChild(this.handle);

        this.element.addEventListener('mousedown', function (event) {
          var el = this.element, o = 0;
          do {
            o += el.offsetLeft;
            el = el.offsetParent;
          } while (el);

          this.origin = o;
          this.width = this.bar.offsetWidth;
          this.drag = true;
          return false;
        }.bind(this));

        document.addEventListener('mousemove', function (event) {
          if (!that.drag) return;
          tangle.setValue('slice', Math.max(0, Math.min(1, (event.pageX - that.origin) / that.width)));
        });
        document.addEventListener('mouseup', function () {
          that.drag = false;
        });
      },

      update: function (element, value) {
        this.handle.style.left = (100*value) + '%';
      },
    },

    Tangle.classes.VolumetricView = {
      initialize: function (element, options, tangle, variables) {
        var that = this;

        this.tangle = tangle;
        this.element = element;

        this.width = 364;
        this.height = 384;
        this.depth = 256;

        this.resX = 182;
        this.resY = 192;
        this.slices = 108;
        this.stride = 8;
        this.rows = Math.ceil(this.slices / this.stride);

        this.createSlices();

        var load = 0;
        this.image = new Image();
        this.image.onload = function () {
          if (++load == 2) that.drawSlices(); 
        };

        this.mask = new Image();
        this.mask.onload = function () {
          if (++load == 2) that.drawSlices(); 
        };

//        this.image.src = 'data/MRbrain.png';
        this.image.src = '/files/mri/MRbrain-color.jpg';
        this.mask.src = '/files/mri/MRbrain-alpha8.png';
      },

      update: function (element, value) {
        var l = Math.abs(Math.cos(value)) > Math.abs(Math.sin(value));

        if (this.l != l || this.slice != slice) {
          var slice = this.tangle.getValue('slice'), index, 
              n = (l ? Math.cos(value) : Math.sin(value)) > 0,
              sn = n ? slice : 1 - slice;

          index = +(this.slicesX.length * sn);

          var display = l ? 'block' : 'none';
          forEach(this.slicesX, function (el, i) {
            el.style.display = display;

            var opacity;
            if (i >= index) {
              opacity = n ? .95 : .001;
            }
            else {
              opacity = !n ? .95 : .001;
            }

            el.style.opacity = opacity;
          });

          index = +(this.slicesZ.length * sn);

          var display = !l ? 'block' : 'none';
          forEach(this.slicesZ, function (el, i) {
            el.style.display = display;

            var opacity;
            if (i >= index) {
              opacity = n ? .95 : .001;
            }
            else {
              opacity = !n ? .95 : .001;
            }

            el.style.opacity = opacity;
          });

          this.slice = slice;
          this.l = l;
        }
      },

      createSlices: function () {
        this.element.innerHTML = '';
        this.ctxX = [];
        this.ctxZ = [];

        // X slices
        for (var i = 0; i < this.slices; ++i) {
          var z = -((i / this.slices) - .5) * this.depth,
              t = 'translateZ(' + z + 'px) translateX(70px)';

          var canvas = document.createElement('canvas');
          canvas.className = 'x';
          canvas.width = this.resX;
          canvas.height = this.resY;
          canvas.style.width = this.width + 'px';
          canvas.style.height = this.height + 'px';
          canvas.style.WebkitTransform = t;
          canvas.style.MozTransform = t;
          canvas.style.transform = t;
          canvas.style.position = 'absolute';

          this.element.appendChild(canvas);
          this.ctxX.push(canvas.getContext('2d'));
        }

        // Z slices
        for (var i = 0; i < this.resX; ++i) {
          var z = -(this.depth - this.width) / 2,
              x = ((i / this.resX) - .5) * this.width,
              t = 'translateX(' + x + 'px) translateX(70px) rotateY(90deg) translateZ(' + z + 'px)';

          var canvas = document.createElement('canvas');
          canvas.className = 'z';
          canvas.width = this.slices;
          canvas.height = this.resY;
          canvas.style.width = this.depth + 'px';
          canvas.style.height = this.height + 'px';
          canvas.style.WebkitTransform = t;
          canvas.style.MozTransform = t;
          canvas.style.transform = t;
          canvas.style.opacity = 0;
          canvas.style.position = 'absolute';

          this.element.appendChild(canvas);
          this.ctxZ.push(canvas.getContext('2d'));
        }

        this.slicesX = this.element.querySelectorAll('canvas.x');
        this.slicesZ = this.element.querySelectorAll('canvas.z');
      },

      drawSlices: function () {

        var s = this.stride,
            sl = this.slices,
            r = this.rows,
            w = this.resX,
            h = this.resY,
            img = this.image,
            mask = this.mask,
            ctxX = this.ctxX,
            ctxZ = this.ctxZ;

        var alpha, color;

        // X slices
        forEach(this.slicesX, function (slice, i) {
          var c = ctxX[i],
              ox = (i % s) * w, oy = Math.floor(i / s) * h;

          // Draw alpha channel and get pixels
          c.drawImage(mask, ox, oy, w, h, 0, 0, w, h);
          alpha = c.getImageData(0, 0, w, h);

          // Draw color channel and get pixels
          c.drawImage(img, ox, oy, w, h, 0, 0, w, h);
          color = c.getImageData(0, 0, w, h);

          // Copy red to alpha.
          var src = alpha.data, dst = color.data;
          for (var y = 0; y < h; ++y) {
            for (var x = 0; x < w; ++x) {
              var o = (x + y * w) * 4;
              dst[o + 3] = src[o];
            }
          }

          // Draw RGBA.
          c.putImageData(color, 0, 0);
        });

        // Z slices
        forEach(this.slicesZ, function (slice, i) {
          var c = ctxZ[i];

          // Render transposed slices as vertical strips.
          for (var j = 0; j < sl; ++j) {
            var ox = (j % s) * w, oy = Math.floor(j / s) * h;

            // Draw alpha channel
            c.drawImage(mask, ox + i, oy, 1, h, j, 0, 1, h);
          }

          // Get pixels
          alpha = c.getImageData(0, 0, w, h);

          // Render transposed slices as vertical strips.
          for (var j = 0; j < sl; ++j) {
            var ox = (j % s) * w, oy = Math.floor(j / s) * h;

            // Draw color channel
            c.drawImage(img, ox + i, oy, 1, h, j, 0, 1, h);
          }
          // Get pixels
          color = c.getImageData(0, 0, w, h);

          // Copy red to alpha.
          var src = alpha.data, dst = color.data;
          for (var y = 0; y < h; ++y) {
            for (var x = 0; x < w; ++x) {
              var o = (x + y * w) * 4;
              dst[o + 3] = src[o];
            }
          }

          // Draw RGBA.
          c.putImageData(color, 0, 0);
        });
      },

    };

    var tangle = new Tangle(document.querySelector('#head-3d'), model);

}, 200);
// -->
</script>

<script>
// <!--
//
//  Tangle.js
//  Tangle 0.1.0
//
//  Created by Bret Victor on 5/2/10.
//  (c) 2011 Bret Victor.  MIT open-source license.

var Tangle = this.Tangle = function (rootElement, modelClass) {

    var tangle = this;
    tangle.element = rootElement;
    tangle.setModel = setModel;
    tangle.getValue = getValue;
    tangle.setValue = setValue;
    tangle.setValues = setValues;

    var _model;
    var _nextSetterID = 0;
    var _setterInfosByVariableName = {};   //  { varName: { setterID:7, setter:function (v) { } }, ... }
    var _varargConstructorsByArgCount = [];


    //
    // construct

    initializeElements();
    setModel(modelClass);
    return tangle;


    //
    // elements

    function initializeElements() {
        var elements = rootElement.getElementsByTagName("*");
        var interestingElements = [];
        
        // build a list of elements with class or data-var attributes
        
        for (var i = 0, length = elements.length; i < length; i++) {
            var element = elements[i];
            if (element.getAttribute("class") || element.getAttribute("data-var")) {
                interestingElements.push(element);
            }
        }

        // initialize interesting elements in this list.  (Can't traverse "elements"
        // directly, because elements is "live", and views that change the node tree
        // will change elements mid-traversal.)
        
        for (var i = 0, length = interestingElements.length; i < length; i++) {
            var element = interestingElements[i];
            
            var varNames = null;
            var varAttribute = element.getAttribute("data-var");
            if (varAttribute) { varNames = varAttribute.split(" "); }

            var views = null;
            var classAttribute = element.getAttribute("class");
            if (classAttribute) {
                var classNames = classAttribute.split(" ");
                views = getViewsForElement(element, classNames, varNames);
            }
            
            if (!varNames) { continue; }
            
            var didAddSetter = false;
            if (views) {
                for (var j = 0; j < views.length; j++) {
                    if (!views[j].update) { continue; }
                    addViewSettersForElement(element, varNames, views[j]);
                    didAddSetter = true;
                }
            }
            
            if (!didAddSetter) {
                var formatAttribute = element.getAttribute("data-format");
                var formatter = getFormatterForFormat(formatAttribute, varNames);
                addFormatSettersForElement(element, varNames, formatter);
            }
        }
    }
            
    function getViewsForElement(element, classNames, varNames) {   // initialize classes
        var views = null;
        
        for (var i = 0, length = classNames.length; i < length; i++) {
            var clas = Tangle.classes[classNames[i]];
            if (!clas) { continue; }
            
            var options = getOptionsForElement(element);
            var args = [ element, options, tangle ];
            if (varNames) { args = args.concat(varNames); }
            
            var view = constructClass(clas, args);
            
            if (!views) { views = []; }
            views.push(view);
        }
        
        return views;
    }
    
    function getOptionsForElement(element) {   // might use dataset someday
        var options = {};

        var attributes = element.attributes;
        var regexp = /^data-[\w\-]+$/;

        for (var i = 0, length = attributes.length; i < length; i++) {
            var attr = attributes[i];
            var attrName = attr.name;
            if (!attrName || !regexp.test(attrName)) { continue; }
            
            options[attrName.substr(5)] = attr.value;
        }
         
        return options;   
    }
    
    function constructClass(clas, args) {
        if (typeof clas !== "function") {  // class is prototype object
            var View = function () { };
            View.prototype = clas;
            var view = new View();
            if (view.initialize) { view.initialize.apply(view,args); }
            return view;
        }
        else {  // class is constructor function, which we need to "new" with varargs (but no built-in way to do so)
            var ctor = _varargConstructorsByArgCount[args.length];
            if (!ctor) {
                var ctorArgs = [];
                for (var i = 0; i < args.length; i++) { ctorArgs.push("args[" + i + "]"); }
                var ctorString = "(function (clas,args) { return new clas(" + ctorArgs.join(",") + "); })";
                ctor = eval(ctorString);   // nasty
                _varargConstructorsByArgCount[args.length] = ctor;   // but cached
            }
            return ctor(clas,args);
        }
    }
    

    //
    // formatters

    function getFormatterForFormat(formatAttribute, varNames) {
        if (!formatAttribute) { formatAttribute = "default"; }

        var formatter = getFormatterForCustomFormat(formatAttribute, varNames);
        if (!formatter) { formatter = getFormatterForSprintfFormat(formatAttribute, varNames); }
        if (!formatter) { log("Tangle: unknown format: " + formatAttribute); formatter = getFormatterForFormat(null,varNames); }

        return formatter;
    }
        
    function getFormatterForCustomFormat(formatAttribute, varNames) {
        var components = formatAttribute.split(" ");
        var formatName = components[0];
        if (!formatName) { return null; }
        
        var format = Tangle.formats[formatName];
        if (!format) { return null; }
        
        var formatter;
        var params = components.slice(1);
        
        if (varNames.length <= 1 && params.length === 0) {  // one variable, no params
            formatter = format;
        }
        else if (varNames.length <= 1) {  // one variable with params
            formatter = function (value) {
                var args = [ value ].concat(params);
                return format.apply(null, args);
            };
        }
        else {  // multiple variables
            formatter = function () {
                var values = getValuesForVariables(varNames);
                var args = values.concat(params);
                return format.apply(null, args);
            };
        }
        return formatter;
    }
    
    function getFormatterForSprintfFormat(formatAttribute, varNames) {
        if (!sprintf || !formatAttribute.test(/\%/)) { return null; }

        var formatter;
        if (varNames.length <= 1) {  // one variable
            formatter = function (value) {
                return sprintf(formatAttribute, value);
            };
        }
        else {
            formatter = function (value) {  // multiple variables
                var values = getValuesForVariables(varNames);
                var args = [ formatAttribute ].concat(values);
                return sprintf.apply(null, args);
            };
        }
        return formatter;
    }

    
    //
    // setters
    
    function addViewSettersForElement(element, varNames, view) {   // element has a class with an update method
        var setter;
        if (varNames.length <= 1) {
            setter = function (value) { view.update(element, value); };
        }
        else {
            setter = function () {
                var values = getValuesForVariables(varNames);
                var args = [ element ].concat(values);
                view.update.apply(view,args);
            };
        }

        addSetterForVariables(setter, varNames);
    }

    function addFormatSettersForElement(element, varNames, formatter) {  // tangle is injecting a formatted value itself
        var span = null;
        var setter = function (value) {
            if (!span) { 
                span = document.createElement("span");
                element.insertBefore(span, element.firstChild);
            }
            span.innerHTML = formatter(value);
        };

        addSetterForVariables(setter, varNames);
    }
    
    function addSetterForVariables(setter, varNames) {
        var setterInfo = { setterID:_nextSetterID, setter:setter };
        _nextSetterID++;

        for (var i = 0; i < varNames.length; i++) {
            var varName = varNames[i];
            if (!_setterInfosByVariableName[varName]) { _setterInfosByVariableName[varName] = []; }
            _setterInfosByVariableName[varName].push(setterInfo);
        }
    }

    function applySettersForVariables(varNames) {
        var appliedSetterIDs = {};  // remember setterIDs that we've applied, so we don't call setters twice
    
        for (var i = 0, ilength = varNames.length; i < ilength; i++) {
            var varName = varNames[i];
            var setterInfos = _setterInfosByVariableName[varName];
            if (!setterInfos) { continue; }
            
            var value = _model[varName];
            
            for (var j = 0, jlength = setterInfos.length; j < jlength; j++) {
                var setterInfo = setterInfos[j];
                if (setterInfo.setterID in appliedSetterIDs) { continue; }  // if we've already applied this setter, move on
                appliedSetterIDs[setterInfo.setterID] = true;
                
                setterInfo.setter(value);
            }
        }
    }
    

    //
    // variables

    function getValue(varName) {
        var value = _model[varName];
        if (value === undefined) { log("Tangle: unknown variable: " + varName);  return 0; }
        return value;
    }

    function setValue(varName, value) {
        var obj = {};
        obj[varName] = value;
        setValues(obj);
    }

    function setValues(obj) {
        var changedVarNames = [];

        for (var varName in obj) {
            var value = obj[varName];
            var oldValue = _model[varName];
            if (oldValue === undefined) { log("Tangle: setting unknown variable: " + varName);  continue; }
            if (oldValue === value) { continue; }  // don't update if new value is the same

            _model[varName] = value;
            changedVarNames.push(varName);
        }
        
        if (changedVarNames.length) {
            applySettersForVariables(changedVarNames);
            updateModel();
        }
    }
    
    function getValuesForVariables(varNames) {
        var values = [];
        for (var i = 0, length = varNames.length; i < length; i++) {
            values.push(getValue(varNames[i]));
        }
        return values;
    }

                    
    //
    // model

    function setModel(modelClass) {
        var ModelClass = function () { };
        ModelClass.prototype = modelClass;
        _model = new ModelClass;

        updateModel(true);  // initialize and update
    }
    
    function updateModel(shouldInitialize) {
        var ShadowModel = function () {};  // make a shadow object, so we can see exactly which properties changed
        ShadowModel.prototype = _model;
        var shadowModel = new ShadowModel;
        
        if (shouldInitialize) { shadowModel.initialize(); }
        shadowModel.update();
        
        var changedVarNames = [];
        for (var varName in shadowModel) {
            if (!shadowModel.hasOwnProperty(varName)) { continue; }
            if (_model[varName] === shadowModel[varName]) { continue; }
            
            _model[varName] = shadowModel[varName];
            changedVarNames.push(varName);
        }
        
        applySettersForVariables(changedVarNames);
    }


    //
    // debug

    function log (msg) {
        if (window.console) { window.console.log(msg); }
    }

};  // end of Tangle


//
// components

Tangle.classes = {};
Tangle.formats = {};

Tangle.formats["default"] = function (value) { return "" + value; };

// -->
</script>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Making Love to WebKit]]></title>
    <link href="https://acko.net/blog/making-love-to-webkit/"/>
    <updated>2012-01-09T00:00:00+01:00</updated>
    <id>https://acko.net/blog/making-love-to-webkit</id>
    <content type="html"><![CDATA[<div class="g8 i2 first"><div class="pad">
  <h2 class="sub">Parallax, GPUs and Technofetishism</h2>

  <p>
    If the world is going to end in 2012, Acko.net will at least go out in style: I've redesigned. Those of you reading through RSS readers will want to <a href="http://acko.net/">enter through the front door</a> in a WebKit-browser like Chrome, Safari or even an iPad.
  </p>

  <p class="bubble">
    The last design was meant to feel spacious, the new design <em>is</em> spacious, thanks to generous use of CSS 3D transforms.
  </p>

  <h3>CSS 3D vs. WebGL</h3>
  
  <p>
    This idea started with an accidental discovery: if you put a CSS perspective on a scrollable &lt;DIV&gt;, then 3D elements inside that &lt;DIV&gt; will retain their perspective while you scroll. This results in smooth, native parallax effects, and makes objects jump out of the page, particularly when using an analog input device with inertial scrolling.
  </p>

  <p>
    This raises the obvious question: how far can you take it? Of course, this only works on WebKit browsers, who currently have the only CSS 3D implementation out of beta, so it's not a viable strategy by itself yet. IE10 and Firefox will be the next browsers to offer it. There's WebGL in Chrome and Firefox that can be used to do similar things, but WebGL is its own sandbox: you can't put DOM elements in there, or use native interaction. And any amount of WebGL rendering in response to e.g. scrolling is going to involve some amount of lag. Still, I wasn't going put a lot of effort into making a CSS 3D-only design without some backup.
  </p>

  <p>
    That's why I actually built the whole thing on top of <a href="https://github.com/mrdoob/three.js/">Three.js</a>, mrdoob's excellent JavaScript 3D engine. Aside from providing a comprehensive standard library for 3D manipulation, it also lets you swap out the rendering component. Out of the box, it can render to a 2D canvas, a WebGL canvas, or SVG.
  </p>

</div></div>
<div class="g7"><div class="pad">

  <h3>The DOM Scenegraph</h3>
  
  <p>
    So I augmented it with a CSS 3D renderer (<a href="https://github.com/unconed/CSS3D.js">GitHub</a>). It reads out the scene and renders each object using DOM elements, shaped and transformed into the right 3D position, orientation and appearance. They sit ‘in’ the page, and the browser projects and composits them for you. Of course, this only works for simple geometric shapes like lines or rectangles, but luckily that's all I need.
  </p>
  
  <p>
    It would be too slow to have to render out new elements for every frame, so the CSS 3D renderer's elements persist. Moving or rotating an object involves just changing a CSS property. Same for the camera: the entire scene is wrapped in a &lt;DIV&gt; that has its own 3D transform.
  </p>

  <p>
    So it's VRML all over again, but this time, it actually sort of performs. With our browsers being actual 3D engines, it's not a huge leap from here to having a &lt;MESH&gt; tag in HTML6, can-of-worms-factor not withstanding.
  </p>

  <p>
    Having built a quick prototype, I was satisfied with how well it worked, particularly in Safari on OS X, where the cross-pollination from the iPhone's mature tile-based GPU renderer has clearly paid off and there is no lag at all.
  </p>

</div></div>

<aside class="g5">
  <p class="m3"><img src="/files/making-love-to-webkit/dom.png" alt="CSS 3D DOM" />The DOM tree of this page. Yup, nasty.</p>
</aside>

<div class="c"></div>

<aside class="g5">
  <p class="m3"><img src="/files/making-love-to-webkit/old-acko.png" alt="Acko.net old design" />Previous design (<a href="/tag/acko.net">Archive</a>)</p>
  <p><img src="/files/making-love-to-webkit/sketch.jpg" alt="Initial sketch" />Initial sketch</p>
  <p><img src="/files/making-love-to-webkit/editor.png" alt="Initial sketch" />Scene editor</p>
</aside>

<div class="g7"><div class="pad">

  <h3>
    Design Process
  </h3>

  <p>
    Now all that was needed was a design. Last time I drew out a manual perspective drawing in Illustrator, which was tedious, but still basically came down to designing a flat image. This time, it would have to work in 3D. I started with a quick sketch to get a feel for the perspective, now that it no longer needed to double as a flat frame for the site's content.
  </p>
  
  <p>
    Simple geometric shapes, parallel lines, consistent angles. Simple enough. But if real perspective was involved, I would have to place items so they would look good from multiple angles, and each would need convincing depth and shading. To do this all by hand, typing out coordinates and perpetually refreshing the page, would take forever.
  </p>
  
  <p>
    So instead I built a simple editor to speed up the process. It's super ghetto, and basically just exists to manipulate the colors, positions and orientations of objects in a Three.js scene. It spits out a JSON object describing them, which can then be unserialized again into a scene.
  </p>

  <p>
    This also helped maintain a consistent palette. The colors are built from a few base tints, brightened or darked in linear RGB—i.e. before gamma correction. This ensures even tones and allowed for easy color adjustments.
  </p>

  <p>
    The editor is almost entirely keyboard operated, but with its minimum amount of features I was at least able to place items in 3D, copy/paste objects and see it from any angle or position I wanted. To 'save', I just copied the output into a .JS file, where I could make manual tweaks too if necessary.
  </p>

  <p>
    As for the actual site and content, I wanted to keep it much more sober. Like many others these days, I want to treat blogging more like publishing. That way I can focus on crafting each post more like an article with illustrations and asides rather than just a text blog.
  </p>
    
  <p>
    Hence, while there's a big party upstairs, it's all <a href="http://www.amazon.com/Elements-Typographic-Style-Robert-Bringhurst/dp/0881791326">typography</a> down below. The font of choice is <a href="http://processtypefoundry.com/fonts/klavika/">Klavika</a>, a humanist/geometric sans-serif with just the right kind of “Dutch Art Museum Signage” meets “Cyberpunk” I was looking for. The layout is a responsive multi-column grid that collapses down for smaller screens and devices. Finally, a strict vertical rhythm is enforced in the lines to keep everything nice and tidy.
  </p>
  
</div></div>

<div class="g9"><div class="pad">
  <h4>Editor</h4>
  <iframe frameborder="0" src="/files/slacko/load.html" width="680" height="580"></iframe>
  <p class="m0 l0">
    <a href="http://acko.net/files/slacko/editor.html" target="_blank" class="editor-open">Open editor in new window</a>
  </p>
</div></div>

<div class="g3"><div class="pad">
  <h4>Controls</h4>
  <ul class="flat">
    <li><kbd>Click</kbd>+<kbd>Drag</kbd> — Orbit camera</li>

    <li><kbd>Enter</kbd> — New object</li>
    <li><kbd>Space</kbd> — Clone object</li>
    <li><kbd>Backspace</kbd> — Delete object</li>
    <li><kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd><br />Cycle through objects</li>

    <li><kbd>W</kbd><kbd>A</kbd><kbd>S</kbd><kbd>D</kbd>&nbsp; <kbd>Q</kbd><kbd>E</kbd><br />Move object</li>
    <li><kbd>Shift</kbd>+<kbd>W</kbd><kbd>A</kbd><kbd>S</kbd><kbd>D</kbd> &nbsp; <kbd>Q</kbd><kbd>E</kbd><br />Resize object</li>
    <li><kbd>Ctrl</kbd>+<kbd>W</kbd><kbd>A</kbd><kbd>S</kbd><kbd>D</kbd> &nbsp; <kbd>Q</kbd><kbd>E</kbd><br />Move camera</li>

    <li><kbd>[</kbd><kbd>]</kbd> — Lower/raise units</li>
    <li><kbd>Z</kbd><kbd>X</kbd><br />Orbit distance</li>
    <li><kbd>T</kbd>/<kbd>T</kbd>/<kbd>U</kbd><br />Tag/untag/untag all</li>
  </ul>
</div></div>

<div class="g8 i2"><div class="pad">
  <h3>She cannae take the power cap'n!</h3>
  <p>
    307 objects later it was finished, and not a single image was used. Unfortunately, as you can see there are tons of glitches in the editor—though some objects only have one side by design, and it works a lot better in a separate window. CSS 3D was never meant to do this, and you often see incorrect depth layering and flickering. Luckily most of these are caused by the floating grid markers and aren't a problem in the final view. The rest was resolved by splitting up objects or dual layering problematic surfaces, but some minor problems remain. Also for some reason, the background &lt;DIV&gt;'s click areas extend beyond their visible area, causing some click layering issues that I had to work around. Text resizing in the browser also leads to breakage, though multi-touch zoom works in Safari.
  </p>
  
  <p>
    Performance in Safari is wonderfully smooth too, but Chrome OS X starts to lag a bit. Luckily the effects are turned off as soon as they go off screen, so any lag should be confined to the top of the page. Finally, there's also a random bug where sometimes the page will refuse to scroll if the mouse is over a 3D object, which is unfortunate, but also near-impossible to reproduce reliably.
  </p>
  
  <p>
    In theory the iPad would perform second, but it has its own issues. The use of page-in-page scrolling disables inertia, but this is entirely beyond my control. The other issue is that sometimes, the iPad will decide to render the page content at lower resolution, making it hard to read. I guess the CSS wizardry confuses its GPU texture management. A refresh usually fixes this.
  </p>
  
  <p>
    I also discovered some funny ways of abusing CSS 3D for weird effects. If you have a WebKit browser, scroll to the top and enter the Konami code for an impressionistic version of the same thing.
  </p>
    
  <p>
    I guess I'm now the proud owner of the first unofficial CSS 3D ‘ACID’ test. I'm eager to see how the next browser handles it. If it ends up being a silly idea in the long run, I can always just switch the output to WebGL, but for now I'm willing to run with it. I put in a universal CSS 3D detector and prefixes for all the major browsers.
  </p>
  
  <p>
    For non-CSS 3D browsers, I simply rendered the header into a static image. It's not as fun without the shifting perspective, but it adds its own kind of optical illusion as you scroll down.
  </p>
  
  <h3>
    Putting it all together
  </h3>
  
  <p>
    To power the site, I got rid of Drupal and replaced it with the nimble <a href="http://jekyllrb.com/">Jekyll</a>. Hat tip to <a href="http://walkah.net/">James Walker</a>, who did the same thing just a few days earlier and put all the code on GitHub to learn from.
  </p>
    
  <p>
    I've been really impressed with Jekyll's simple workflow, and though it's all static HTML, it's a refreshing change of pace. And thanks to client-side JS, it doesn't preclude adding interactive elements at all. I can treat my site as just a database of documents retrievable over HTTP, and wrap the logic around that.
  </p>
  
  <p>
    So I created a nice client-side navigator that transitions between pages, using 2D transforms, which also work on Firefox. It uses the HTML5 pushState API and replaces regular links with AJAX requests. Aside from being a faster way to navigate around, it also lets me link up multiple articles in a series elegantly. When you go back to a previous screen, it literally presses the browser's back button, thus avoiding creating a long, useless history trail. You go back exactly the way you came, scrolling back to where you were, just like the real back/forward buttons do. For example, click over to my <a href="/blog/making-worlds-introduction/">Making Worlds</a> series of posts. You can come back right away.
  </p>
  
  <p>
    I didn't use any libraries or router frameworks for this, simply because I wanted to have done it all myself at least once. As it now says on my <a href="/about">About page</a>, quoting Feynman: <em>"What I cannot create, I do not understand"</em>. The only way to grok the intricacies of something like browser history state, which we all use every day, is to dive in and replicate it. Otherwise, you'll just take carefully choreographed behavior for granted and your mental model will be incomplete.
  </p>

  <p>
    To keep code size down, I compiled a custom build of Three.js with only the parts I need. I also used YUI compressor to minify the CSS and JS. However, I don't mean to obfuscate the code: the important bits will make their way onto Github soon enough.
  </p>
  
  <p><em>Update: The CSS 3D renderer and editor are now <a href="https://github.com/unconed/CSS3D.js">available on GitHub</a>.</em></p>
  
  <h3>
    And Done?
  </h3>

  <p>
    I migrated over most of the content and did some house cleaning while I was at it. Most things should be back, but further fixes will be made. I also haven't implemented any commenting solution so far, but I'll be adding it back somehow as soon as I figure something out. In the mean time, there's <a href="https://plus.google.com/112457107445031703644/posts/HDJMgpDRAey">a Google Plus thread</a>.
  </p>

  <p>
    The final result looks like something that would perhaps once unironically be labeled <strong>The Information Superhighway</strong> in a magazine from the 90s, though with less neon green. I like it.
  </p>

</div></div>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[On TermKit]]></title>
    <link href="https://acko.net/blog/on-termkit/"/>
    <updated>2011-05-17T00:00:00+02:00</updated>
    <id>https://acko.net/blog/on-termkit</id>
    <content type="html"><![CDATA[<div class="g8 i2 first"><div class="pad">
  
<p>I've been administering Unix machines for many years now, and frankly, it kinda sucks. It makes me wonder, when sitting in front of a crisp, 2.3 million pixel display (i.e. a laptop) why I'm telling those pixels to draw me a computer terminal from the 80s.
</p>

<p>
<img src="/files/termkit/termkit-1.png" alt="Regular bash terminal" />
</p>

<p>
And yet, that's what us tech nerds do every day. The default Unix toolchain, marked in time by the 1970 epoch, operates in a world where data is either binary or text, and text is displayed in monospace chunks. The interaction is strictly limited to a linear flow of keystrokes, always directed at only one process. And that process is capable of communicating only in short little grunts of text, perhaps coalescing into a cutesy little ASCII art imitation of things that grown-ups call "dialogs", "progress bars", "tables" and "graphs".
</p>

<p>
The <a href="http://www.faqs.org/docs/artu/ch01s06.html">Unix philosophy</a> talks about software as a toolset, about tiny programs that can be composed seamlessly. The principles are sound, and have indeed stood the test of time. But they were implemented in a time when computing resources were orders of magnitude smaller, and computer interaction was undiscovered country.
</p>

<p>
In the meantime, we've gotten a lot better at displaying information. We've also learned a lot of lessons through the web about data interchange, network transparency, API design, and more. We know better how small tweaks in an implementation can make a world of difference in usability.
</p>

<p>
And yet the world of Unix is rife with jargon, invisible processes, traps and legacy bits. Every new adept has to pass a constant trial by fire, of not destroying their system at every opportunity it gives them.
</p>

<p>
So while I agree that having a flexible toolbox is great, in my opinion, those pieces could be built a lot better. I don't want the computer equivalent of a screwdriver and a hammer, I want a tricorder and a laser saw. TermKit is my attempt at making these better tools and addresses a couple of major pain points.
</p>

<p>
I see TermKit as an extension of what Apple did with OS X, in particular the system tools like Disk Utility and Activity Monitor. Tech stuff doesn't have to look like it comes from the Matrix.
</p>

<h2>Rich Display</h2>

<p>
It's 2011, and monospace text just doesn't cut it anymore. In the default ANSI color palette, barely any of the possible color combinations are even readable. We can't display graphs, mathematical formulas, tables, etc. We can't use the principles of modern typography to lay out information in a readable, balanced way.
</p>

<p>
<img src="/files/termkit/termkit-2.png" alt="TermKit example" />
</p>

<p>
So instead, I opted for a front-end built in WebKit. Programs can display anything that a browser can, including HTML5 media. The output is built out of generic widgets (lists, tables, images, files, progress bars, etc.). The goal is to offer a rich enough set for the common data types of Unix, extensible with plug-ins. The back-end streams display output to the front-end, as a series of objects and commands.
</p>

<p>
I should stress that despite WebKit it is not my intent to make HTML the lingua franca of Unix. The front-end is merely implemented in it, as it makes it instantly accessible to anyone with HTML/CSS knowledge.
</p>

<h2>Pipes</h2>

<p>
  Unix pipes are anonymous binary streams, and each process comes with at least three: Standard In, Standard Out and Standard Error. This corresponds to the typical <em>Input</em> &gt; <em>Processing</em> &gt; <em>Output</em> model, with an additional error channel. However, in actual usage, there are two very different scenarios.
</p>

<p>
<img src="/files/termkit/termkit-3.png" alt="" />
</p>

<p>
One is the case of interactive usage: a human watches the program output (from Std Out) on a display, and types keystrokes to interact with it (into Std In). Another case is the data processing job: a program accepts a data stream in a particular format on Std In, and immediately outputs a related data stream on Std Out. These two can be mixed, in that a chain of piped commands can have a human at either end, though usually this implies non-interactive operation.
</p>

<p>
These two cases are shoehorned into the same pipes, but happen quite differently. Human input is spontaneous, sporadic and error prone. Data input is strictly formatted and continuous. Human output is ambiguous, spaced out and wordy. Data output is conservative and monolithic.
</p>

<p>
As a result, many Unix programs have to be careful about data. For example, many tools dynamically detect whether they are running in interactive mode, and adjust their output to be more human-friendly or computer-friendly. Other tools come with flags to request the input/output in specific formats.
</p>

<p>
This has lead to "somewhat parseable text" being the default interchange format of choice. This seems like an okay choice, until you start to factor in the biggest lesson learned on the web: there is no such thing as plain text. Text is messy. Text-based formats lie at the basis of every SQL injection, XSS exploit and encoding error. And it's in text-parsing code where you'll likely find buffer overflows.
</p>

<p>
What this means in practice is that in every context, there are some forbidden characters, either by convention or by spec. For example, no Unicode or spaces in filenames. In theory, it's perfectly fine, but in practice, there's at least one shell script on your system that would blow up if you tried. Despite the promise of text as the universal interchange format, we've been forced to impose tons of vague limits.
</p>

<p>
So how do we fix this? By separating the "data" part from the "human" part. Then we can use messy text for humans, and pure data for the machines. Enter "Data In/Out", "View In/Out".
</p>

<p>
<img src="/files/termkit/termkit-4.png" alt="TermKit data flow diagram" />
</p>

<p>
The data pipes correspond to the classical Std pipes, with one difference: the stream is prefixed with MIME-like headers (Content-Type, Content-Length, etc). Of these, only the 'Content-Type' is required. It allows programs to know what kind of input they're receiving, and handle it graciously without sniffing. Aside from that, the data on the pipe is a raw binary stream.
</p>

<p>
The view pipes carry the display output and interaction to the front-end. Widgets and UI commands are streamed back and forth as JSON messages over the view pipes.
</p>

<p>
The real magic happens when these two are combined. The last dangling Std Out pipe of any command chain needs to go into the Terminal, to be displayed as output. But the data coming out of Data Out is not necessarily human-friendly.
</p>

<p>
Thanks to the MIME-types, we can solve this universally. TermKit contains a library of output formatters which each handle a certain type of content (text, code, images, ...). It selects the right formatter based on the Content-Type, which then generates a stream of view updates. These go over the View Out pipe and are added to the command output.
</p>

<p>
<img src="/files/termkit/termkit-7.png" alt="Cat'ing an image" />
</p>

<p>
As a result, you can <code>cat</code> a PNG and have it just work. TermKit <code>cat</code> doesn't know how to process PNGs or HTML—it only guesses the MIME type based on the filename and pipes the raw data to the next process. Then the formatter sends the image to the front-end. If you <code>cat</code> a source code file, it gets printed with line numbers and syntax highlighting.
</p>

<p>
So where does "somewhat parseable text" fit in? It turns out to be mostly unnecessary. Commands like <code>ls</code> output structured data by nature, i.e. a listing of files from one or more locations. It makes sense to pipe around this data in machine-form. Output flags like <code>ls&nbsp;-l</code> become mere hints for the final display, which can toggle on-the-fly between compact and full listing.
</p>

<p>
In TermKit's case, JSON is the interchange format of choice. The <code>Content-Type</code> for file listings is <code>application/json;&nbsp;schema=termkit.files</code>. The <code>schema</code> acts as a marker to select the right output plug-in. In this case, we want the file formatter rather than the generic raw JSON formatter.
</p>

<p>
<img src="/files/termkit/termkit-8.png" alt="Formatting data in TermKit" />
</p>

<p>
Isn't JSON data harder to work with than lines of text? Only in some ways, but parsing JSON is trivial these days in any language. Because of this, I built TermKit <code>grep</code> so it supports grepping JSON data recursively. This happens transparently when the input is <code>application/json</code> instead of <code>text/plain</code>. As a result <code>ls&nbsp;|&nbsp;grep</code> works as you'd expect it to.
</p>

<p>
To slot in traditional Unix utilities in this model, we can pipe their data as <code>application/octet-stream</code> to start with, and enhance specific applications with type hints and wrapper scripts.
</p>

<p>
<img src="/files/termkit/termkit-6.png" alt="" />
</p>

<p>
Finally, having type annotations on pipes opens up another opportunity: it allows us to pipe in HTTP GET / POST requests almost transparently. Getting a URL becomes no different from catting a file, and both can have fancy progress bars, even when inside a pipe chain like <code>get&nbsp;|&nbsp;grep</code>.
</p>

<h2>Synchronous interaction</h2>

<p>
All interaction in a traditional terminal is synchronous. Only one process is interactive at a time, and each keystroke must be processed by the remote shell before it is displayed. This leads to an obvious daily frustration: SSH keystroke lag.
</p>

<p>
To fix this, TermKit is built out of a separate front-end and back-end. The front-end can run locally, controlling a back-end on a remote machine. The connection can be tunneled over SSH for security.
</p>

<p class="tc">
<a href="https://github.com/unconed/TermKit/raw/master/Mockups/Architecture.pdf"><img src="/files/termkit/termkit-9.png" alt="TermKit architecture" />Architecture diagram</a> (TK stands for TermKit)
</p>

<p>
Additionally, all display updates and queries are asynchronous. The WebKit-based HTML display is split up into component views, and the view pipes of each subprocess are routed to their own view. Vice-versa, any interactive widgets inside a view can send callback messages back to their origin process, as long as it's still running.
</p>

<p>
This also allows background processes to work without overflowing the command prompt.
</p>

<h2>String-based command line</h2>

<p>
A lot of my frustration comes from bash's arcane syntax. It has a particularly nasty variant of C-style escaping. Just go ahead and <em>try</em> to match a regular expression involving both types of quotes.
</p>

<p>
But at its core, a bash command is a series of tokens. Some tokens are single words, some are flags, some are quoted strings, some are modifiers (like | and &gt;). It makes sense for the input to reflect this.
</p>

<p>
<img src="/files/termkit/termkit-5.png" alt="TermKit command-line" />
</p>

<p>
TermKit's input revolves around tokenfield.js, a new snappy widget with plenty of tricks. It can do auto-quoting, inline autocomplete, icon badges, and more. It avoids the escaping issue altogether, by always processing the command as tokens rather than text. Keys that trigger special behaviors (like a quote) can be pressed again to undo the behavior and just type one character.
</p>

<p>
The behaviors are encoded in a series of objects and regexp-based triggers, which transform and split tokens as they are typed. That means it's extensible too.
</p>

<h2>Usability</h2>

<p>
At the end of the day, Unix just has bad usability. It tricks us with unnecessary abbreviations, inconsistent arguments (-r vs -R) and nitpicky syntax. Additionally, Unix has a habit of giving you raw data, but not telling you useful facts, e.g. 'r-xr-xr-x' instead of "You can't touch this" (<em>ba-dum tsshh</em>).
</p>

<p>
One of the Unix principles is nobly called "Least Surprise", but in practice, from having observed new Unix users, I think it often becomes "Maximum Confusion". We should be more pro-active in nudging our users in the right direction, and our tools should be designed for maximum discoverability.
</p>

<p>
For example, I want to see the relevant part of a man page in a tooltip when I'm typing argument switches. I'd love for dangerous flags to be highlighted in red. I'd love to see regexp hints of possible patterns inline.
</p>

<p>
There's tons to be done here, but we can't do anything without modern UI abilities.
</p>

<h2>Focus and Status</h2>

<p>
With a project like TermKit, it's easy to look at the shiny exterior and think "meh", or that I'm just doing things differently for difference's sake. But to me, the real action is under the hood. With a couple of tweaks and some uncompromising spring cleaning, we can get Unix to do a lot more for us.
</p>

<p>
The current version of TermKit is just a rough alpha, and what it does is in many ways just parlour tricks compared to what it could be doing in a few months. The architecture definitely supports it.
</p>

<p>
I've worked on TermKit off and on for about a year now, so I'd love to hear feedback and ideas. Please <a href="http://github.com/unconed/TermKit">go check out the code</a>.
</p>

<p>
TermKit owes its existence to Node.js, Socket.IO, jQuery and WebKit. Thanks to everyone who has contributed to those projects.
</p>

<p>
<em>Edit, a couple of quick points:</em>
</p>

<ul>
<li>A Linux port will definitely happen, since it's built out of WebKit and Node.js. Whoever does it first gets a cookie.</li>
<li>TermKit is not tied to JSON except in its own internal communication channels. TermKit Pipes can be in any format, and old-school plain-text still works. JSON just happens to be very handy and very lightweight.</li>
<li>The current output is just a proof of concept and lacks many planned usability enhancements. There are mockups on github.</li>
<li>If you're going to tell me I'm stupid, please read all the other 100 comments doing so first, so we can keep this short for everyone else.</li>
</ul>

<p>
<em>Edit, random fun:</em>
</p>

<p>Someone asked for AVS instead of TermKit in the comments... best I could do was JS1K with a PDF surprise:</p>

<iframe style="margin: 0 auto;" width="425" height="349" src="http://www.youtube.com/embed/dAeZTgRuWsU" frameborder="0" allowfullscreen="allowfullscreen"></iframe>

<iframe width="560" height="349" src="http://www.youtube.com/embed/_6Z5dnlfcls" frameborder="0" allowfullscreen="allowfullscreen"></iframe>

</div></div>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[My JS1K Demo - The Making Of]]></title>
    <link href="https://acko.net/blog/js1k-demo-the-making-of/"/>
    <updated>2010-08-06T00:00:00+02:00</updated>
    <id>https://acko.net/blog/js1k-demo-the-making-of</id>
    <content type="html"><![CDATA[<div class="g8 i2 first"><div class="pad">
  
<p>If you haven't seen it yet, check out the <a href="http://js1k.com">JS1K demo contest</a>. The goal is to do something neat in 1 kilobyte of JavaScript code.</p>
<p>I couldn't resist making one myself, so I pulled out my bag of tricks from my <a href="/design/avs">Winamp music visualization</a> days and started coding. I'm really happy with how it turned out. And no, it won't work in Internet Explorer 8 or less.</p>

<p><em>Edit: OH SNAP! I just rewrote the demo to include volumetric light beams and still fit in 1K:</em></p>

</div></div>

<div class="g6"><div class="pad">

<h3>Original Version</h3>
<p><iframe id="js1kjs" frameborder="0" style="background:#000;border:0" width="460" height="345" src="about:blank"></iframe></p>
<p><button style="display: none" id="js1kjs-stop" onclick="document.querySelector('#js1kjs').setAttribute('src','about:blank');this.style.display='none';document.querySelector('#js1kjs-start').style.display='inline-block';">Stop Demo</button><button id="js1kjs-start" onclick="document.querySelector('#js1kjs').setAttribute('src','/files/making-of-js1k/1022b.html');this.style.display='none';document.querySelector('#js1kjs-stop').style.display='inline-block';">Start Demo</button><button onclick="document.querySelector('#js1kjs').setAttribute('src','/files/making-of-js1k/1022s.html');document.querySelector('#js1kjs-start').style.display='inline-block';document.querySelector('#js1kjs-stop').style.display = 'none';">View Source</button></p>

</div></div>
<div class="g6"><div class="pad">

<h3>Improved Version</h3>

<p><iframe id="js1kjs2" frameborder="0" style="background:#000;border:0" width="460" height="345" src="about:blank"></iframe></p>
<p><button style="display: none" id="js1kjs2-stop" onclick="document.querySelector('#js1kjs2').setAttribute('src','about:blank');this.style.display = 'none';document.querySelector('#js1kjs2-start').style.display='inline-block';">Stop Demo</button><button id="js1kjs2-start" onclick="document.querySelector('#js1kjs2').setAttribute('src','/files/making-of-js1k/1024b.html');this.style.display='none';document.querySelector('#js1kjs2-stop').style.display='inline-block';">Start Demo</button><button onclick="document.querySelector('#js1kjs2').setAttribute('src','/files/making-of-js1k/1024s.html');document.querySelector('#js1kjs2-start').style.display='inline-block';document.querySelector('#js1kjs2-stop').style.display='none';">View Source</button></p>

</div></div>

<div class="g8 i2"><div class="pad">

<p>Now, whenever size is an issue, the best way to make a small program is to generate all data on the fly, i.e. procedurally. This saves valuable storage space. While this might seem like a black art, often it just comes down to clever use of (high school) math. And as is often the case, the best tricks are also the simplest, as they use the least amount of code.</p>
<p>To illustrate this, I'm going to break down my demo and show you all the major pieces and shortcuts used. Unlike the actual 1K demo, the code snippets here will feature legible spacing and descriptive variable names.</p>

<h3>Initialization</h3>

<p>JS1K's rules give you a Canvas tag to work with, so the first piece of code initializes it and makes it fill the window.</p>
<p>From then on, it just renders frames of the demo. There are four major parts to this:</p>
<ul>
<li>Animating the wires</li>
<li>Rotating and projecting the wires into the camera view</li>
<li>Coloring the wires</li>
<li>Animating the camera</li>
</ul>
<p>All of this is done 30 times per second, using a normal <code>setInterval</code> timer:</p>
<p class="codeblock"><code>setInterval(function () { ... }, 33);</code></p>

<h3>Drawing Wires</h3>

<p>The most obvious trick is that everything in the demo is drawn using only a single primitive: a line segment of varying color and stroke width. This allows the whole drawing process to be streamlined into two tight, nested loops. Each inner iteration draws a new line segment from where the previous one ended, while the outer iteration loops over the different wires.</p>

<p><img class="natural" src="/files/making-of-js1k/drawing-process.png" alt="drawing process for demo" title="Drawing each wire in sequence." /></p>

<p>The lines are blended additively, using the built-in 'lighten' mode, which means they can be drawn in any order. This avoids having to manually sort them back-to-front.</p>
<p>To simplify the perspective transformations, I use a coordinate system that places the point (0, 0) in the center of the canvas and ranges from -1 to 1 in both coordinates. This is a compact and convenient way of dealing with varying window sizes, without using up a lot of code:</p>
<p class="codeblock"><code>with (graphics) {<br />&nbsp; ratio = width / height;<br />&nbsp; globalCompositeOperation = &#039;lighter&#039;;<br />&nbsp; scale(width / 2 / ratio, height / 2);<br />&nbsp; translate(ratio, 1);<br />&nbsp; lineWidthFactor = 45 / height;<br />&nbsp; ...</code></p>
<p>I add a correction <code>ratio</code> for non-square windows and calculate a reference line width <code>lineWidthFactor</code> for later. Here, I'm using the <code>with</code> construct to save valuable code space, though its use is generally discouraged. </p>
<p>Then there's the two nested <code>for</code> loops: one iterating over the wires, and one iterating over the individual points along each wire. In pseudo-code they look like:</p>
<p class="codeblock"><code>For (12 wires =&gt; wireIndex) {<br />&nbsp; Begin new wire<br />&nbsp; For (45 points along each wire =&gt; pointIndex) {<br />&nbsp;&nbsp;&nbsp; Calculate path of point on a sphere: (x,y,z)<br />&nbsp;&nbsp;&nbsp; Extrude outwards in swooshes: (x,y,z)<br />&nbsp;&nbsp;&nbsp; Translate and Rotate into camera view: (x,y,z)<br />&nbsp;&nbsp;&nbsp; Project to 2D: (x,y)<br />&nbsp;&nbsp;&nbsp; Calculate color, width and luminance of this line: (r,g,b) (w,l)<br />&nbsp;&nbsp;&nbsp; If (this point is in front of the camera) {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; If (the last point was visible) {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Draw line segment from last point to (x,y)<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp; else {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Mark this point as invisible<br />&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp; Mark beginning of new line segment at (x,y)<br />&nbsp; }<br />}</code></p>

<h3>Mathbending</h3>

<p>To generate the wires, I start with a formula which generates a sinuous path on a sphere, using latitude/longitude. This controls the tip of each wire and looks like:</p>
<p class="codeblock"><code>offset = time - pointIndex * 0.03 - wireIndex * 3;<br />longitude = cos(offset + sin(offset * 0.31)) * 2<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; + sin(offset * 0.83) * 3 + offset * 0.02;<br />latitude = sin(offset * 0.7) - cos(3 + offset * 0.23) * 3;</code></p>
<p>This is classic procedural coding at its best: take a time-based <code>offset</code> and plug it into a random mish-mash of easily available functions like cosine and sine. Tweak it until it 'does the right thing'. It's a very cheap way of creating interesting, organic looking patterns.</p>
<p>This is more art than science, and mostly just takes practice. Any time spent with a graphical calculator will definitely pay off, as you will know better which mathematical ingredients result in which shapes or patterns. Also, there are a couple of things you can do to maximize the appeal of these formulas.</p>
<p>First, always include some non-linear combinations of operators, e.g. nesting the sin() inside the cos() call. Combined, they are more interesting than when one is merely overlaid on the other. In this case, it turns regular oscillations into time-varying frequencies.</p>
<p>Second, always scale different wave periods using prime numbers. Because primes have no factors in common, this ensures that it takes a very long time before there is a perfect repetition of all the individual periods. Mathematically, the <a href="http://www.wolframalpha.com/input/?i=%28least+common+multiple+of+%28100+31+83+70+23%29%29+%2F+100">least common multiple of the chosen periods</a> is huge (414253 units ~ 4.8 hours). Plotting the longitude/latitude for <code>offset = 0..600</code> you get:</p>
<p><img class="natural" src="/files/making-of-js1k/js1k-2.jpg" alt="a pseudo-random set of oscillations" title="Looks pretty random" /></p>
<p>The graph looks like a random tangled curve, with no apparent structure, which makes for motions that never seem to repeat. If however, you reduce each constant to only a single significant digit (e.g. 0.31 -&gt; 0.3, 0.83 -&gt; 0.8), then suddenly repetition becomes apparent:</p>
<p><img class="natural" src="/files/making-of-js1k/js1k-3.jpg" alt="a not so pseudo-random set of oscillations" title="Not so random" /></p>
<p>This is because the least common multiple has <a href="http://www.wolframalpha.com/input/?i=%28least+common+multiple+of+%28100+30+80+70+20%29%29+%2F+100">dropped to 84 units ~ 3.5 seconds</a>. Note that both formulas have the same code complexity, but radically different results. This is why all procedural coding involves some degree of creative fine tuning.</p>
<h3>Extrusion</h3>

<p>Given the formula for the tip of each wire, I can generate the rest of the wire by sweeping its tail behind it, delayed in time. This is why <code>pointIndex</code> appears as a negative in the formula for <code>offset</code> above. At the same time, I move the points outwards to create long tails.</p>
<p>I also need to convert from lat/long to regular 3D XYZ, which is done using the <a href="http://en.wikipedia.org/wiki/Spherical_coordinate_system">spherical coordinate transform</a>:</p>
<p><img class="natural" src="/files/making-of-js1k/js1k-4.jpg" alt="spherical coordinates" /><center>Source: <a href="http://en.wikipedia.org/wiki/File:Coord_system_SE_0.svg">Wikipedia</a></center></p>
<p class="codeblock"><code>distance = f.sqrt(pointIndex+.2);<br />x = cos(longitude) * cos(latitude) * distance;<br />y = sin(longitude) * cos(latitude) * distance;<br />z = sin(latitude) * distance;</code></p>
<p>You might notice that rather than making <code>distance</code> a straight up function of the length <code>pointIndex</code> along the wire, I applied a square root. This is another one of those procedural tricks that seems arbitrary, but actually serves an important visual purpose. This is what the square root looks like (solid curve):</p>
<p><img class="natural" src="/files/making-of-js1k/js1k-5.jpg" alt="square root" /></p>
<p>The dotted curve is the square root's derivative, i.e. it indicates the slope of the solid curve. Because the slope goes down with increasing distance, this trick has the effect of slowing down the outward motion of the wires the further they get. In practice, this means the wires are more tense in the middle, and more slack on the outside. It adds just enough faux-physics to make the effect visually appealing.</p>
<h3>Rotation and Projection</h3>

<p>Once I have absolute 3D coordinates for a point on a wire, I have to render it from the camera's point of view. This is done by moving the origin to the camera's position (X,Y,Z), and applying two rotations: one around the vertical (yaw) and one around the horizontal (pitch). It's like spinning on a wheely chair, while tilting your head up/down.</p>
<p class="codeblock"><code>x -= X; y -= Y; z -= Z;<br /><br />x2 = x * cos(yaw) + z * sin(yaw);<br />y2 = y;<br />z2 = z * cos(yaw) - x * sin(yaw);<br /><br />x3 = x2;<br />y3 = y2 * cos(pitch) + z2 * sin(pitch);<br />z3 = z2 * cos(pitch) - y2 * sin(pitch);</code></p>
<p>The camera-relative coordinates are projected in perspective by dividing by Z — the further away an object, the smaller it is. Lines with negative Z are behind the camera and shouldn't be drawn. The width of the line is also scaled proportional to distance, and the first line segment of each wire is drawn thicker, so it looks like a plug of some kind:</p>
<p class="codeblock"><code>plug = !pointIndex;<br />lineWidth = lineWidthFactor * (2 + plug) / z3;<br />x = x3 / z3;<br />y = y3 / z3;<br /><br />lineTo(x, y);<br />if (z3 &gt; 0.1) {<br />&nbsp; if (lastPointVisible) {<br />&nbsp;&nbsp;&nbsp; stroke();<br />&nbsp; }<br />&nbsp; else {<br />&nbsp;&nbsp;&nbsp; lastPointVisible = true;<br />&nbsp; }<br />}<br />else {<br />&nbsp; lastPointVisible = false;<br />}<br />beginPath();<br />moveTo(x, y);</code></p>
<p>There's a subtle optimization hiding in plain sight here. Because I'm drawing lines, I need both the previous and the current point's coordinates at each step. To avoid using variables for these, I place the <code>moveTo</code> command at the end of the loop rather than at the beginning. As a result, the previous coordinates are remembered transparently into the next iteration, and all I need to do is make sure the first call to <code>stroke()</code> doesn't happen.</p>

<h3>Coloring</h3>
<p>Each line segment also needs an appropriate coloring. Again, I used some trial and error to find a simple formula that works well. It uses a sine wave to rotate overall luminance in and out of the (Red, Green, Blue) channels in a deliberately skewed fashion, and shifts the R component slowly over time. This results in a nice varied palette that isn't overly saturated.</p>
<p class="codeblock"><code>pulse = max(0, sin(time * 6 - pointIndex / 8) - 0.95) * 70;<br />luminance = round(45 - pointIndex) * (1 + plug + pulse);<br />strokeStyle=&#039;rgb(&#039; + <br />&nbsp; round(luminance * (sin(plug + wireIndex + time * 0.15) + 1)) + &#039;,&#039; + <br />&nbsp; round(luminance * (plug + sin(wireIndex - 1) + 1)) + &#039;,&#039; +<br />&nbsp; round(luminance * (plug + sin(wireIndex - 1.3) + 1)) +<br />&nbsp; &#039;)&#039;;</code></p>
<p>Here, <code>pulse</code> causes bright pulses to run across the wires. I start with a regular sine wave over the length of the wire, but truncate off everything but the last 5% of each crest to turn it into a sparse pulse train:</p>
<p><img class="natural" src="/files/making-of-js1k/js1k-6.jpg" alt="sine pulse train" /></p>
<h3>Camera Motion</h3>

<p>With the main visual in place, almost all my code budget is gone, leaving very little room for the camera. I need a simple way to create consistent motion of the camera's X, Y and Z coordinates. So, I use a neat low-tech trick: repeated interpolation. It looks like this:</p>
<p class="codeblock"><code>sample += (target - sample) * fraction</code></p>
<p><code>target</code> is set to a random value. Then, every frame, <code>sample</code> is moved a certain fraction towards it (e.g. <code>0.1</code>). This turns <code>sample</code> into a smoothed version of <code>target</code>. Technically, this is a <em>one-pole low-pass filter</em>.</p>
<p>This works even better when you apply it twice in a row, with an intermediate value being interpolated as well:</p>
<p class="codeblock"><code>intermediate += (target - intermediate) * fraction<br />sample += (intermediate - sample) * fraction</code></p>
<p>A sample run with <code>target</code> being changed at random might look like this:</p>
<p><img class="natural" src="/files/making-of-js1k/js1k-7.jpg" alt="sine pulse train" /></p>
<p>You can see that with each interpolation pass, more discontinuities get filtered out. First, jumps are turned into kinks. Then, those are smoothed out into nice bumps.</p>
<p>In my demo, this principle is applied separately to the camera's X, Y and Z positions. Every ~2.5 seconds a new target position is chosen:</p>
<p class="codeblock"><code>if (frames++ &gt; 70) {<br />&nbsp; Xt = random() * 18 - 9;<br />&nbsp; Yt = random() * 18 - 9;<br />&nbsp; Zt = random() * 18 - 9;<br />&nbsp; frames = 0;<br />}<br /><br />function interpolate(a,b) {<br />&nbsp; return a + (b-a) * 0.04;<br />}<br /><br />Xi = interpolate(Xi, Xt);<br />Yi = interpolate(Yi, Yt);<br />Zi = interpolate(Zi, Zt);<br /><br />X&nbsp; = interpolate(X,&nbsp; Xi);<br />Y&nbsp; = interpolate(Y,&nbsp; Yi);<br />Z&nbsp; = interpolate(Z,&nbsp; Zi);</code></p>
<p>The resulting path is completely smooth and feels quite dynamic.</p>
<h3>Camera Rotation</h3>

<p>The final piece is orienting the camera properly. The simplest solution would be to point the camera straight at the center of the object, by calculating the appropriate <code>pitch</code> and <code>yaw</code> directly off the camera's position (X,Y,Z):</p>
<p class="codeblock"><code>yaw&nbsp;&nbsp; = atan2(Z, -X);<br />pitch = atan2(Y, sqrt(X * X + Z * Z));</code></p>
<p>However, this gives the demo a very static, artificial appearance. What's better is making the camera point in the right direction, but with just enough freedom to pan around a bit.</p>
<p>Unfortunately, the 1K limit is unforgiving, and I don't have any space to waste on more 'magic' formulas or interpolations. So instead, I cheat by replacing the formulas above with:</p>
<p class="codeblock"><code>yaw&nbsp;&nbsp; = atan2(Z, -X * 2);<br />pitch = atan2(Y * 2, sqrt(X * X + Z * Z));</code></p>
<p>By multiplying X and Y by 2 strategically, the formula is 'wrong', but the error is limited to about 45 degrees and varies smoothly. Essentially, I gave the camera a lazy eye, and got the perfect dynamic motion with only 4 bytes extra!</p>
<h3>Addendum</h3>

<p>After seeing the other demos in the contest, I wasn't so sure about my entry, so I started working on a version 2. The main difference is the addition of glowy light beams around the object.</p>
<p>As you might suspect, I'm cheating massively here: rather than do physically correct light scattering calculations, I'm just using a 2D effect. Thankfully it comes out looking great.</p>
<p>Essentially, I take the rendered image, and process it in a second Canvas that is hidden. This new image is then layered on the original.</p>
<p>I take the image and repeatedly blend it with a zoomed out copy of itself. With every pass the number of copies doubles, and the zoom factor is squared every time. After 3 passes, the image has been smeared out into an 8 = 2<sup>3</sup> 'tap' radial blur. I lock the zooming to the center of the 3D object. This makes the beams look like they're part of the 3D world rather than drawn on later.</p>
<p>For additional speed, the beam image is processed at half the resolution. As a side effect, the scaling down acts like a slight blur filter for the beams.</p>
<p>Unfortunately, this effect was not very compact, as it required a lot of drawing mode changes and context switches. I had no room for it  in the source code.</p>
<p>So, I had to squeeze out some more room in the original. First, I simplified the various formulas to the bare minimum required for interesting visuals. I replaced the camera code with a much simpler one, and started aggressively shaving off every single byte I could find. Then I got creative, and ended up recreating the secondary canvas every frame just to avoid switching back its state to the default.</p>
<p>Eventually, after a lot of bit twiddling, a version came out that was 1024 bytes long. I had to do a lot of unholy things to get it to fit, but I think the end result is worth it ;). </p>
<h3>Closing Thoughts</h3>

<p>I've long been a fan of the demo scene, and fondly remember <a href="http://www.youtube.com/watch?v=dQveVMQDJlg">Second Reality</a> in 1993 as my introduction to the genre. Since then, I've always looked at math as a tool to be mastered and wielded rather than subject matter to be absorbed.</p>
<p>With this blog post, I hope to inspire you to take the plunge and see where some simple formulas can take you.</p>

</div></div>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[JavaScript audio synthesis with HTML 5]]></title>
    <link href="https://acko.net/blog/javascript-audio-synthesis-with-html-5/"/>
    <updated>2009-08-12T00:00:00+02:00</updated>
    <id>https://acko.net/blog/javascript-audio-synthesis-with-html-5</id>
    <content type="html"><![CDATA[<div class="g8 i2 first"><div class="pad">
    
<aside class="r"><a href="http://acko.net/files/audiosynth/index.html"><img class="natural" src="/files/audiosynth/audio.png" alt="Audio wave" /></a></aside>

<p>
  HTML5 gives us a couple new toys to play with, such as &lt;AUDIO&gt; and &lt;VIDEO&gt; tags. On the visual side, we've already seen <a href="https://developer.mozilla.org/samples/video/chroma-key/index.xhtml">live green-screening</a> with Canvas and JS, and in terms of audio there's been several JS <a href="http://www.randomthink.net/labs/html5drums/">drum machines</a> already. But the question I was interested in was: can you use JavaScript to stream live data into these media tags?
</p>

<p>
Enter the <a href="http://acko.net/files/audiosynth/index.html">JavaScript audio synth</a>. It generates a handful of samples using very basic time-domain synthesis, wraps them up in a WAVE file header and embeds them in &lt;AUDIO&gt; tags using base64-encoded data URIs. Each sample is then triggered using timers to play the drum pattern. It's quite simple to do and runs fast enough in HTML5 capable browsers to be unnoticeable. Yes, it sounds tinny, but that's just because I'm too lazy to design proper filters for toys like this.
<!--break-->
Unfortunately, while the synthesis is fast enough to run real-time, you can't actually use it for a full live audio stream, as there is no way to queue up chunks of synthesized audio for seamless playback. I tried triggering multiple &lt;AUDIO&gt; tags in parallel to address this, but that didn't work either.
</p>

<p>
My final attempt was to generate tons of periodic audio loops only a couple of ms long, and to play them back with looping turned on while altering each tag's volume in real time, hence doing a sort of additive wavetable synthesis. Unfortunately, looping is not a fully supported feature, and the only browser I found that does it (Safari) doesn't loop seamlessly at all.
</p>

<p>
All in all, my first brush with the &lt;AUDIO&gt; tag was a major disappointment. The &lt;VIDEO&gt; tag's high-level approach leads to similar limitations, but they are offset by the flexibility and power of the &lt;CANVAS&gt; tag. Unfortunately, there is no 'audio canvas' to solve similar problems with audio.
</p>

</div></div>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Projective Texturing with Canvas]]></title>
    <link href="https://acko.net/blog/projective-texturing-with-canvas/"/>
    <updated>2008-11-11T00:00:00+01:00</updated>
    <id>https://acko.net/blog/projective-texturing-with-canvas</id>
    <content type="html"><![CDATA[<div class="g8 i2 first"><div class="pad">

<p><em>Update: People keep asking me to use this code, apparently unaware this has been made obsolete by CSS 3D. So it's gone now.</em></p>
  
<p>The <a href="http://en.wikipedia.org/wiki/Canvas_(HTML_element)">Canvas</a> tag's popularity is slowly increasing around the web. I've seen big sites use it for <a href="http://code.google.com/p/jquery-rotate/">image rotation</a>, <a href="http://code.google.com/p/flot/">graph plotting</a>, <a href="http://ajaxian.com/archives/canvas-reflectionjs">reflection effects</a> and much more.
</p>

<p>
However, Canvas is still limited to 2D: its drawing operations can only do typical vector graphics with so-called affine transformations, i.e. scaling, rotating, skewing and translation. Though there have been <a href="https://wiki.mozilla.org/Canvas:3D">some efforts</a> to try and add a 3D context to Canvas, these efforts are still experimental and only available for a minority of browsers through plug-ins.
</p>

<p>
So when my colleague <a href="http://rosshj.com/">Ross</a> asked me if we could build a <a href="http://en.wikipedia.org/wiki/Cover_Flow">Cover Flow</a>-like widget with JavaScript, my initial reaction was no... but really, that's just a cop out. All you need are textured rectangles drawn in a convincing perspective: a much simpler scenario than full blown 3D.
</p>

<p>
<a href="/files/projective-canvas/index.html"><img class="natural" src="/files/projective-canvas/projective-transform.png" alt="" /></a>
</p>

<p>
<!--break-->
Perspective views are described by so-called <em>projective transforms</em>, which Canvas2D does not support. However, it does support arbitrary clipping masks as well as affine transforms of both entire and partial images. These can be used to do a fake projective transform: you cut up your textured surface into a bunch of smaller patches (which are almost-affine) and render each with a normal affine transform. Of course you need to place the patches just right, so as to cover any possible gaps. As long as the divisions are small enough, this looks convincingly 3D.
</p>

<p>
So some hacking later, I have a <a href="/files/projective-canvas/index.html">working projective transform renderer</a> in JavaScript. The algorithm uses adaptive subdivision to maintain quality and can be tuned for detail or performance. At its core it's really just a lot of linear algebra, though I did have to add a bunch of tweaks to make it look seamless due to annoying aliasing effects.
</p>

<p>
Unfortunately Safari seems to be the only browser that can render it at an acceptable speed, so this technique is just a curiosity for now. The current code was mostly written for readability rather than performance though, so it's possible it could be optimized to a more useful state. <strike>Feel free to <a href="/files/projective-canvas/projective.js">browse the code</a></strike>.
</p>

<p>
A real 3D Canvas in the browser would obviously rock, but you can still do some nifty things if you know the right tricks...</p></div></div>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Abusing jQuery.animate for fun and profit (and bacon)]]></title>
    <link href="https://acko.net/blog/abusing-jquery-animate-for-fun-and-profit-and-bacon/"/>
    <updated>2008-09-22T00:00:00+02:00</updated>
    <id>https://acko.net/blog/abusing-jquery-animate-for-fun-and-profit-and-bacon</id>
    <content type="html"><![CDATA[<div class="g8 i2 first"><div class="pad"><p>The days of static UIs that only have jarring transitions between pages are pretty much over. With frameworks like <a href="http://developer.apple.com/documentation/Cocoa/Conceptual/CoreAnimation_guide/Introduction/Introduction.html">CoreAnimation</a> or <a href="http://jquery.com">jQuery</a>, it's easy to add useful animations to applications and webpages. In the case of jQuery, you can easily animate any CSS property, and you get free work-arounds for browser bugs to boot. You can run multiple animations (of arbitrary duration) at the same time, queue animations and even animate complex properties like colors or clipping rectangles.
</p>


<aside class="r m1"><img class="natural" src="/files/bacon/bacon1.png" style="width: 100px" alt="Strip of bacon" /></aside>

<p>But what if you want to go beyond mere CSS? You might have a custom widget that is drawn using <code>&lt;canvas&gt;</code>, whose contents are controlled by internal variables; maybe you're using 3D transformations to scale and position images on a page, and simple 2D tweening just doesn't cut it.
</p>

<p>
In that case, it would seem you are out of luck: jQuery's .animate() method can only be applied to a collection of DOM elements, and relies heavily on the browser's own semantics for processing CSS values and their units. However thanks to JavaScript's flexibility and jQuery's architecture, we can work around this, and re-use jQuery's excellent animation core for our own nefarious purposes.
</p>

<h2>Hackity hack hack</h2>

<p>
First, we need an object to store all the variables we wish to animate. We use an anonymous <code>&lt;div&gt;</code> outside of the main document, so that jQuery's DOM calls still work on it. We simply add our own properties to it:
</p>

<p class="codeblock">
<code>var&nbsp;vars&nbsp;=&nbsp;$.extend($('&lt;div&gt;')[0],&nbsp;{<br />
&nbsp;&nbsp;foo:&nbsp;1,<br />
&nbsp;&nbsp;bar:&nbsp;2,<br />
<br />
&nbsp;&nbsp;customAnimate:&nbsp;true,<br />
&nbsp;&nbsp;updated:&nbsp;true<br />
});<br />
</code>
</p>

<p>
In this case, our properties are <code>foo</code> and <code>bar</code>. We also set <code>customAnimate</code> and <code>updated</code> to identify this object (see below).
</p>

<p>
Next we need to override jQuery's default step function, which gets called for every step of an animation, and applies new values to an element's CSS properties.
</p>

<p class="codeblock">
<code>&nbsp;&nbsp;&nbsp;//&nbsp;jQuery.fx.step._default<br />
&nbsp;&nbsp;&nbsp;&nbsp;_default:&nbsp;function(fx)&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fx.elem.style[fx.prop]&nbsp;=&nbsp;fx.now&nbsp;+&nbsp;fx.unit;<br />
&nbsp;&nbsp;&nbsp;&nbsp;}<br />
</code>
</p>

<p>
We can replace it using the following snippet:
</p>

<p class="codeblock">
<code>var&nbsp;$_fx_step_default&nbsp;=&nbsp;$.fx.step._default;<br />
$.fx.step._default&nbsp;=&nbsp;function&nbsp;(fx)&nbsp;{<br />
&nbsp;&nbsp;if&nbsp;(!fx.elem.customAnimate)&nbsp;return&nbsp;$_fx_step_default(fx);<br />
&nbsp;&nbsp;fx.elem[fx.prop]&nbsp;=&nbsp;fx.now;<br />
&nbsp;&nbsp;fx.elem.updated&nbsp;=&nbsp;true;<br />
};<br />
</code>
</p>

<p>
With the new step function, jQuery will check for the presence of a <code>customAnimate</code> property on any element it is animating. If present, it will assign the (unit-less) value to <code>element.property</code> rather than <code>element.style.property</code> and mark the element by setting <code>element.updated</code> to true.
</p>

<p>
Now we're ready to animate, using the normal <code>$.animate</code> syntax:
</p>

<p class="codeblock">
<code>$(vars).animate({&nbsp;foo:&nbsp;5,&nbsp;bar:&nbsp;10&nbsp;},&nbsp;{&nbsp;duration:&nbsp;1000&nbsp;});<br />
</code>
</p>

<p>
The values <code>vars.foo</code> and <code>vars.bar</code> will now smoothly change over time. You can use any of <a href="http://docs.jquery.com/Effects/animate">jQuery's animation abilities</a> as usual.
</p>

<p>
Now what about that <code>updated</code> variable? Well to actually use these animated values, you will need some kind of timer or step callback to read them back and draw them on the page. If you're using <code>&lt;canvas&gt;</code>, you need to redraw your entire widget for every change, but you don't want to be wasting CPU time by constantly refreshing it. Furthermore, if you're running multiple animations at the same time, you'll want to aggregate all your property changes into a single redraw per frame. This is easy with the <code>updated</code> property and a simple timer:
</p>

<p class="codeblock">
<code>setInterval(function&nbsp;()&nbsp;{<br />
&nbsp;&nbsp;if&nbsp;(!vars.updated)&nbsp;return;<br />
&nbsp;&nbsp;vars.updated&nbsp;=&nbsp;false;<br />
&nbsp;&nbsp;<br />
&nbsp;&nbsp;drawWidget();<br />
},&nbsp;30);<br />
</code>
</p>

<p>
Now your widget will only refresh itself when its values are changed by the animation step function we defined earlier, and very few CPU cycles are wasted. As a plus, you can render updates as fast or as slow as you want, without affecting the duration of your animations.
</p>

<h2>Demo</h2>

<p>
I whipped up a quick demo which renders a <a href="/files/bacon/animation-demo.html">cloud of bacon</a> using <code>&lt;canvas&gt;</code>. All the motion in the demo is created through <code>$.animate()</code>, with a bunch of animations running at once.
</p>

<p>
<small><em>This demo will not work in Internet Explorer, and has only been tested in Firefox 3 and Safari 3.</em></small>
</p>

<p>
This is a rather esoteric example, but there are plenty of useful ways to apply this technique. I've used it to implement <a href="/files/bacon/omgpizza.mov">smooth, beautiful, usable widgets</a>. You can combine multiple motion and opacity animations triggered by clicks and hovers without issues.
</p>
 
<h2>Final notes</h2>

<p>
While this technique works great, there is one big caveat. You should avoid animating any property that exists in CSS ('float', 'display', 'opacity', ...) because these have unexpected side effects depending on the browser.
</p>

<p>
There are also a couple of weaknesses:
</p>

<ul>
  <li>jQuery does not support continued easing. That is, when you override an animation that is already in progress, the variable being animated will instantly stop and restart from its current position. The rate of change is not continuous between the two animations.</li>
  <li>Animating angles is tricky. E.g. when animating from 350˚ to 0˚, you want it to animate across 10˚ and not the long way around. This requires manual correction.</li>
</ul>

<p>
And obviously, it would be cleaner if jQuery's animation core was refactored to separate out the CSS-specific code instead...
</p>

</div></div>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Welcome to the World of Tomorrow!]]></title>
    <link href="https://acko.net/blog/welcome-to-the-world-of-tomorrow/"/>
    <updated>2008-07-20T00:00:00+02:00</updated>
    <id>https://acko.net/blog/welcome-to-the-world-of-tomorrow</id>
    <content type="html"><![CDATA[<div class="g8 i2 first"><div class="pad"><p><small>(with apologies to <a href="http://en.wikipedia.org/wiki/Futurama">Matt Groening</a>)</small>
</p>

<p>
After about <a href="/blog/new-design-for-acko-net">two years</a>, it's time for another make-over of my site.
</p>

<p>
My last design had a relatively quirky look, with a bold red/yellow theme built from various irregular vector shapes. The idea was to step away from the typical mold of rectangular aligned frames on a page. I tried to incorporate some elements of perspective into the page composition, but it ended up being a relatively flat, geometrical theme.
</p>

<p>
This time I wanted to work on the depth aspect and try to create something that feels spacious. To do this, I based the entire redesign on a two-point perspective. While the content itself is normal 2D markup, it sits in a 3D frame.
</p>

<p>
<img class="natural" src="/files/redesign-2008/wirepron.png" title="Some of the guide lines used in the construction process." alt="" /></p>

<p>
<img class="natural" src="/files/making-love-to-webkit/old-acko.png" alt="" /></p>

<p>
The header image is a regular illustration file (which is 100% manual vector work) and the content is typical HTML/CSS. However there is a twist: the perspective from the header is continued into the content with some simple 3D decorations, created on-demand with Canvas tags and JavaScript (<a href="javascript:void(0);" onclick="highlightCanvases();return false;">highlight canvases</a>, check out the footer).
</p>

<p>
While this perspective works perfectly near the top, the further down you go, the more vertically stretched the shapes get and it ends up looking weird. To compromise, the projection actually gets more and more isometric the further down you go. This creates an interesting effect when scrolling down.
</p>

<p>
The design also uses various CSS3 methods (@font-face, text-shadow, box-shadow) throughout, and uses sIFR 3 as a fallback for the headline font. Unfortunately CSS3 is still mostly unsupported in the browserscape, so only Safari 3.1 users get the luxury combo of <em>pretty, fast and no Flash</em>. Everyone else will have to suffer through hacks.
</p>

<p>
As a total surprise, the canvas-rocket-science trickery even works in IE6 thanks to Google's <a href="http://excanvas.sourceforge.net/">ExplorerCanvas</a> library.
</p>

<p>
I'll probably be tweaking it a bit more in the days to come, but feedback is appreciated.
</p>

</div></div>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[ComicJuice gets even better]]></title>
    <link href="https://acko.net/blog/comicjuice-gets-even-better/"/>
    <updated>2007-03-09T00:00:00+01:00</updated>
    <id>https://acko.net/blog/comicjuice-gets-even-better</id>
    <content type="html"><![CDATA[<div class="g8 i2 first"><div class="pad"><p>I finished some more tweaks to ComicJuice:
</p>

<ul>
<li>
<p>IE6 and 7 are now supported, thanks to the amazing <a href="http://excanvas.sourceforge.net/">ExplorerCanvas</a> by Google. It emulates the <a href="http://developer.mozilla.org/en/docs/Drawing_Graphics_with_Canvas">&lt;canvas&gt; tag</a> in IE, meaning that client-side scriptable vector graphics are now available on all the major browsers (IE, Firefox, Safari, Opera). I doubt Konqueror will be far behind.
</p>

<p>
This opens up some cool abilities, like dynamic in-page graphs, mini-widgets (sliders, dials, maps, ...) and even pure JS games. There's a bunch of examples linked on <a href="http://en.wikipedia.org/wiki/Canvas_(HTML_element)">Wikipedia</a> (though most don't use ExplorerCanvas yet).</p>
</li>
<li>
<p>I added support for uploading your own images rather than using pictures on the web. It uses a customized and themed version of core's JS uploader.
</p>
<p class="tc"><img class="natural" src="/files/comicjuice/comicjuice.png" alt="comic juice" /></p>
</li>
<li>I improved the clipping of speech bubbles so there should be less useless whitespace around comics, especially when embedding them.</li>
</ul>

</div></div>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Announcing.... ComicJuice!]]></title>
    <link href="https://acko.net/blog/announcing-comicjuice/"/>
    <updated>2007-03-06T00:00:00+01:00</updated>
    <id>https://acko.net/blog/announcing-comicjuice</id>
    <content type="html"><![CDATA[<div class="g8 i2 first"><div class="pad">
  
<h1>Announcing.... ComicJuice!</h1>
  
<p>I'm proud to announce the start of ComicJuice, a web 2.0 social mashup tool that lets you create comics in your browser and share them with others.
</p>

<p>
<em>Update: Now with Internet Explorer support! Thanks to <a href="http://code.google.com/p/explorercanvas/">Google's ExplorerCanvas</a>. Viewing comics works in IE6 and 7, while editing still requires IE7.</em>
</p>

<p>
<iframe style="margin: 0 auto;" width="425" height="349" src="http://www.youtube.com/embed/qhVMU48GfvE" frameborder="0" allowfullscreen="allowfullscreen"></iframe>
</p>

<p>
The crazy part is that I started working on this only friday evening (that's 4 days ago). Once I had the initial idea and a rough plan, I simply couldn't not code it.
</p>

<p>
A lot of jQuery and JavaScript later, with some &lt;canvas&gt; wizardry (boy is that thing inconsistent across browsers), we have a fully-fledged comic creator. The best part is that all of it is rendered client-side, so no actual images need to be generated. To display a comic, we use the same code as the editing interface. The down-side is that it doesn't work in IE, but I've been thinking about maybe doing a rough canvas emulation. We'll see. For now, the latest versions of Safari, Firefox and Opera have been tested and work well.
</p>

<p>
You can also embed comics with iframes, and copy/pastable code is provided. Like this lame example:
</p>

<p><em>(No longer available)</em></p>

<p>
I figured a Web 2.0 mash-up would not be complete without a fitting design to go in, so I designed icons, sliders and toolbars for the editor, as well as a theme for the website. The theme is a Garland knock-off: I guess I'm proving myself wrong that it's a bad base theme. It's actually quite good as it has fluid/fixed 1-3 column layouts in it.
</p>

<p>
I'm curious to see if ComicJuice takes off and what people do with it. It was a blast to code in any case. Check it out.</p></div></div>
]]></content>
  </entry>
  
</feed>
