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),
...