This week the homework assignment is very simple:

You are going to implement matrices up at the JavaScript level, and then create simple demonstrations that your methods work properly.

Matrices transform points by matrix-vector multiplication. A transformation matrix is a 4×4 array, but to be compatible with WebGL, you should represent your matrix as a column-major array with sixteen elements.

For example, the translation matrix:

10010
01020
00130
0001
should be represented in JavaScript as:
   [ 1,0,0,0, 0,1,0,0, 0,0,1,0, 10,20,30,1 ];

Here are the basic 3D matrix primitives (not including perspective):

identity:
1000
0100
0010
0001
translationMatrix(a,b,c):
100a
010b
001c
0001
xRotationMatrix(θ):
1000
0cos(θ)-sin(θ)0
0sin(θ)cos(θ)0
0001
yRotationMatrix(θ):
cos(θ)0sin(θ)0
0100
-sin(θ)0cos(θ)0
0001
zRotationMatrix(θ):
cos(θ)-sin(θ)00
sin(θ)cos(θ)00
0010
0001
scaleMatrix(a,b,c):
a000
0b00
00c0
0001

Multiplying matrices

To animate objects in a computer graphic scene, you can accumulate changes to a transformation matrix, by multiplying in successive changes to that matrix. As we discussed in class, you should multiply on the right, as follows:

   M2 ← M1 P1
   M3 ← M2 P2
   M4 ← M3 P3
   ...
where Mi is the ith iteration of your transformation matrix, and each Pi is a matrix primitive.

The resulting transformation matrix can then be used to transform all of the points in your object.


Homework

Your homework this week will be to modify the simple animated scene below by one that does more general translation, rotation and scale transformations. Details on how to do this follow below, after the figure.

Next week we will start to tie things back into WebGL, but for now I'd like you everything in JavaScript, by drawing lines on the HTML5 canvas to represent the edges of 3D shapes.

If you select View Source in your browser, you will be able to see the simple canvas declaration for the above figure:

<canvas id=exampleCanvas width=400 height=400 tabindex="1"></canvas>

This is followed by a JavaScript script, which I have also listed below. Your job is to replace the simple translation matrix in that script by more general software that implements and demonstrates all the types of matrix transformation primitives.

In the script listing below, I have highlighted in red the part that you will need to replace. NOTE: To make your code work, you will also need to implement a 4×4 matrix multiply function.

If you are feeling ambitious, you can create multiple cubes or other shapes, such as your own version of a sphere or a cylinder or a torus. As I mentioned in class, a torus can be described as a parametric surface, by sweeping two angles θ and φ between 0 and 2π. Specifically:

x = (R + r cos φ) cos θ
y = (R + r cos φ) sin θ
z = r sin φ

where R is the radius of the large ring, and r is the radius of the tube that circles that ring.


The JavaScript script that is animating the above figure for this document:
<script>

   // GET THE CANVAS ELEMENT AND ITS DRAWING CONTEXT FROM THE DOCUMENT

   var canvas = document.getElementById('exampleCanvas');
   var context = canvas.getContext('2d');

   // THE VERTICES OF A UNIT CUBE

   var pts = [[-1,-1,-1],[ 1,-1,-1],[-1, 1,-1],[ 1, 1,-1],
              [-1,-1, 1],[ 1,-1, 1],[-1, 1, 1],[ 1, 1, 1]];

   // THE EDGES OF A UNIT CUBE (INDEXING INTO THE VERTICES)

   var edges = [[0,1],[2,3],[4,5],[6,7],
                [0,2],[1,3],[4,6],[5,7],
		[0,4],[1,5],[2,6],[3,7]];

   // YOUR FUNCTION THAT GETS CALLED EACH ANIMATION FRAME

   function animate() {

      // GET THE DIMENSIONS OF THE CANVAS

      var w = canvas.width, h = canvas.height;

      // CLEAR THE ENTIRE CANVAS

      context.fillStyle = '#ffffff';
      context.beginPath();
      context.moveTo(0,0);
      context.lineTo(w,0);
      context.lineTo(w,h);
      context.lineTo(0,h);
      context.fill();

      // CREATE THE MATRIX TRANSFORM FOR THIS ANIMATION FRAME.

      ////////////////////////////////////////////////////////////

      // NOTE: THIS IS THE PART THAT YOU WILL BE REPLACING WITH
      // MORE GENERAL KINDS OF TRANSFORMATIONS.

      var x = Math.cos(time) / 2;
      var y = Math.sin(time) / 2;
      var matrix = [ 1,0,0,0, 0,1,0,0, 0,0,1,0, x,y,0,1 ];

      ////////////////////////////////////////////////////////////

      // SET THE DRAWING COLOR TO BLACK

      context.strokeStyle = '#000000';

      // LOOP THROUGH THE EDGES OF THE CUBE

      for (var i = 0 ; i < edges.length ; i++) {

         // TRANSFORM THE EDGE'S TWO ENDPOINTS BY THE MATRIX

         var p0 = transform(pts[edges[i][0]], matrix);
         var p1 = transform(pts[edges[i][1]], matrix);

         // ADD DEPTH PERSPECTIVE

	 var a = depthPerspective(p0);
	 var b = depthPerspective(p1);

	 // DRAW THE EDGE AS A 2D LINE ON THE CANVAS

         context.beginPath();
         context.moveTo(w/2 + w/4 * a[0], h/2 - w/4 * a[1]);
         context.lineTo(w/2 + w/4 * b[0], h/2 - w/4 * b[1]);
         context.stroke();
      }
   }

   // TRANSFORM A POINT BY A MATRIX

   function transform(p, m) {
      return [ m[0] * p[0] + m[4] * p[1] + m[ 8] * p[2] + m[12],
               m[1] * p[0] + m[5] * p[1] + m[ 9] * p[2] + m[13],
               m[2] * p[0] + m[6] * p[1] + m[10] * p[2] + m[14]];
   }

   // APPLY A SIMPLE DEPTH PERSPECTIVE TRANSFORM

   var focalLength = 8.0;

   function depthPerspective(p) {
      var pz = focalLength / (focalLength - p[2]);
      return [p[0] * pz, p[1] * pz, pz];
   }

//--- BOILERPLATE CODE TO SUPPORT ANIMATED DRAWING ON AN HTML CANVAS ---

   var startTime = (new Date()).getTime(), time = startTime;
   window.requestAnimFrame = (function(callback) {
      return window.requestAnimationFrame ||
             window.webkitRequestAnimationFrame ||
             window.mozRequestAnimationFrame ||
             window.oRequestAnimationFrame ||
             window.msRequestAnimationFrame ||
             function(callback) { window.setTimeout(callback, 1000/60); };
   })();
   function tick() {
      time = ((new Date()).getTime() - startTime) / 1000;
      animate();
      requestAnimFrame(function() { tick(); });
   }
   tick();

//----------------------------------------------------------------------

</script>