animating one million letters

by Douglas Duhaime

HTML

<script src="http://www.html5rocks.com/en/tutorials/webgl/million_letters/Three.js"></script>
<script src="http://www.html5rocks.com/en/tutorials/webgl/million_letters/book.js"></script>
<script src="http://www.html5rocks.com/en/tutorials/webgl/million_letters/dat.gui.min.js"></script>
<script id="vertex" type="text/x-glsl-vert">
      varying float vZ;
      uniform float time;
      uniform float effectAmount;
      varying vec2 vUv;

      mat3 rotateAngleAxisMatrix(float angle, vec3 axis) {
        float c = cos(angle);
        float s = sin(angle);
        float t = 1.0 - c;
        axis = normalize(axis);
        float x = axis.x, y = axis.y, z = axis.z;
        return mat3(
          t*x*x + c,    t*x*y + s*z,  t*x*z - s*y,
          t*x*y - s*z,  t*y*y + c,    t*y*z + s*x,
          t*x*z + s*y,  t*y*z - s*x,  t*z*z + c
        );
      }

      vec3 rotateAngleAxis(float angle, vec3 axis, vec3 v) {
        return rotateAngleAxisMatrix(angle, axis) * v;
      }

      void main() {
        float idx = floor(position.y/1.1)*80.0 + floor(position.x/1.1);
        vec3 corner = vec3(floor(position.x/1.1)*1.1, floor(position.y/1.1)*1.1, 0.0);
        vec3 mid = corner + vec3(0.5, 0.5, 0.0);
        vec3 rpos = rotateAngleAxis(idx+time, vec3(mod(idx,16.0), -8.0+mod(idx,15.0), 1.0), position - mid) + mid;
        vec4 fpos = vec4( mix(position,rpos,effectAmount), 1.0 );
        fpos.x += -35.0;
        fpos.z += ((sin(idx+time*2.0)))*4.2*effectAmount;
        fpos.y += ((cos(idx+time*2.0)))*4.2*effectAmount;
        vec4 mvPosition = modelViewMatrix * fpos;
        mvPosition.y += 10.0*sin(time*0.5+mvPosition.x/25.0)*effectAmount;
        mvPosition.x -= 10.0*cos(time*0.5+mvPosition.y/25.0)*effectAmount;
        vec4 p = projectionMatrix * mvPosition;
        vUv = uv;
        vZ = p.z;
        gl_Position = p;
      }
    </script>
    <script id="fragment" type="text/x-glsl-frag">
      varying float vZ;
      varying vec2 vUv;
      uniform float time;
     ...

JavaScript

var renderer = new THREE.WebGLRenderer({
  antialias: true
});
renderer.setClearColor(0xffffff);
document.body.appendChild(renderer.domElement);

var camera = new THREE.PerspectiveCamera(45, 1, 4, 40000);
camera.setLens(35);

window.onresize = function() {
  renderer.setSize(window.innerWidth, window.innerHeight);
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
};
window.onresize();

var radius = 60;

var scene = new THREE.Scene();
camera.position.z = radius;
scene.add(camera);

var fontSize = 16;
var lettersPerSide = 16;
var c = document.createElement('canvas');
c.width = c.height = fontSize * lettersPerSide;
var ctx = c.getContext('2d');
ctx.font = fontSize + 'px Monospace';
var i = 0;

for (var y = 0; y < lettersPerSide; y++) {
  for (var x = 0; x < lettersPerSide; x++, i++) {
    var ch = String.fromCharCode(i);
    ctx.fillText(ch, x * fontSize, -(8 / 32) * fontSize + (y + 1) * fontSize);
  }
}

var tex = new THREE.Texture(c);
tex.flipY = false;
tex.needsUpdate = true;

var mat = new THREE.MeshBasicMaterial({
  map: tex
});
mat.transparent = true;

var geo = new THREE.Geometry();
var str = BOOK;

var j = 0,
  ln = 0;

for (i = 0; i < str.length; i++) {
  var code = str.charCodeAt(i);
  var cx = code % lettersPerSide;
  var cy = Math.floor(code / lettersPerSide);
  var v, t;
  geo.vertices.push(
    new THREE.Vector3(j * 1.1 + 0.05, ln * 1.1 + 0.05, 0),
    new THREE.Vector3(j * 1.1 + 1.05, ln * 1.1 + 0.05, 0),
    new THREE.Vector3(j * 1.1 + 1.05, ln * 1.1 + 1.05, 0),
    new THREE.Vector3(j * 1.1 + 0.05, ln * 1.1 + 1.05, 0)
  );
  var face = new THREE.Face3(i * 4 + 0, i * 4 + 1, i * 4 + 2);
  geo.faces.push(face);
  face = new THREE.Face3(i * 4 + 0, i * 4 + 2, i * 4 + 3);
  geo.faces.push(face);
  var ox = (cx + 0.05) / lettersPerSide,
    oy = (cy + 0.05) / lettersPerSide,
    off = 0.9 / lettersPerSide;
  var sz = lettersPerSide * fontSize;
  geo.faceVertexUvs[0].push([
    new THREE.Vector2(ox, oy + off),
   ...