Homework 4, due Monday, Oct 13.

Sorry it took me a little longer to describe this completely. I wanted to make sure that the on-line class notes were all integrated in properly. :-)

When you have finished the assignment below, post the working applet and the source code onto your class web site. You don't need to send email.

This assignment is all about kinematics. That means you're going to make things move! As we described in class on Monday, this is a leap from making individual objects move around, where each object is moved by an independent 4×4 transformation matrix, to making entire coordinated systems of objects move, through a hierarchy (or tree) of transformation matrices.

Your assignment is make an interesting and original animated scene that has such a hierarchy of moving objects in it. Your scene can consist of one or more human figures, or cows or dragons, automobiles, martian creatures, clocks, butterflies, or anything else that strikes your fancy. Make sure to have fun with it.

It is very important that you read the part at the bottom of this assignment about matrix multiplication order. Otherwise you'll have trouble getting things right.

To review: the way you were doing things before, at each animation frame you were able to string together a set of transformations to make a transformation matrix. Using that technique, at each frame you could construct a matrix, and then transform all your points by that matrix. Below is a typical example:

   double matrix[][] = new double[4][4];                       // TRANSFORMATION MATRIX; CHANGES EACH FRAME
   double time = 0;                                            // TIME COUNTER TO ADVANCE THE ANIMATION
   double cube[][] = {{-1,-1,-1},{1,-1,-1},{-1,1,-1},{1,1,-1}, // THE VERTICES OF AN *UNTRANSFORMED* CUBE
                      {-1,-1, 1},{1,-1, 1},{-1,1, 1},{1,1, 1}};
   double temp[][] = new double[cube.length][3];               // STORE THE TRANSFORMED VERTICES EACH FRAME
   int cubeEdges[][] = {{0,1},{1,3},{3,2},{2,0}, ... };        // EDGES OF THE CUBE (PAIRS OF VERTEX INDICES)


   public void render(Graphics g) {


      double x = Math.cos(2 * Math.PI * time);                 // COMPUTE TIME-VARYING PARAMETERS
      double y = Math.sin(2 * Math.PI * time);
      time += 0.01;

      Matrix.identity(matrix);         // CLEAR THE MATRIX
      Matrix.translate(matrix, x,y,0); // MOVE IN A CIRCLE
      Matrix.scale(matrix, .1,.1,.1);  // SCALE SMALLER

      for (int i = 0 ; i < cube.length ; i++)                  // TRANSFORM THE CUBE BY THE MATRIX
	 Matrix.transform(cube[i], temp[i], matrix);

      for (int i = 0 ; i < cubeEdges.length ; i++) {           // DRAW EDGES BETWEEN THE TRANSFORMED POINTS
	 int i0 = cubeEdges[i][0];
	 int i1 = cubeEdges[i][0];
	 g.drawLine(ViewPortX(temp[i0]), ViewPortY(temp[i0],
	            ViewPortX(temp[i1]), ViewPortY(temp[i1]);

Notice that the above example built a single transformation matrix in each frame, in steps, and then used that single matrix to transform geometry before rendering it.

The key advance we'll be working on is to describe a scene as an entire tree of transformations. At every node of this tree, some part of your scene can be transformed and then rendered. As we discussed, the data structure that lets you traverse a tree is a stack, so you'll need a matrix stack, such as:

   double mStack[][][] = new double[STACKSIZE][4][4];
and a top-of-stack pointer:
   int mTop = 0;
At each frame of your animation the stack needs to start off in its "initial" state:
   mTop = 0;
Then to animate the scene for that frame of animation, you would do a sequence of operations that use this matrix stack. Some of these operations modify the matrix stack itself; others use the matrix at the top of the stack to transform geometry in your scene.

There are several sorts of operations you can perform:

  1. You can modify the matrix on top of the stack;
  2. You can push or pop the matrix stack;
  3. You can use the matrix on top of the stack to transform and render some shape.
Let's take these one at at time:

You already know how to modify a single matrix, by using the methods identity, translate, rotateX, rotateY, rotateZ and scale. For example, you can modify the matrix on top of the stack with such operations as:

   Matrix.rotateX(mStack[mTop], theta);
Also you'll want to be able to push and pop the matrix, to reflect entering and then leaving local branches of the tree that describes the scene (such as the arms and legs of a human figure). A push operation can be effected by:
   Matrix.copy(mStack[mTop], mStack[mTop+1]);
and a pop operation can be effected by:
I encourage you to create push() and pop() methods for this. Make sure that you add error handling to these methods to check for stack overflow or underflow!

Finally, while you're traversing the tree you'll want to render various parts of your scene which are embedded in various "nodes" of the tree. you'll need to transform the actual objects in your scene (arms, legs, trees, automobile parts, etc). You can do this by transforming them by whatever matrix is on top of the stack when that node is reached. For example, if you have a sphere mesh, you might have the following lines of code somewhere:

   Matrix.copy(mStack[mTop], sphMesh.matrix);

Matrix multiplication order:

There is something that might seem counter-intuitive about how you need to multiply the matrices together when traversing the heirarchy tree of your scene. In general, there are two distinct ways you could do matrix multiplication upon a matrix A, by another matrix B:

B × A
A × B
Because matrix multiplication is not commutative, these two operations will usually produce different results. Up to now you might have been doing the first method, which is called pre-multiplication of matrix A by matrix B. The second method, in contrast, is called the post-multiplication of matrix A by matrix B.

It turns out that when you want to make hierarchies, you need to post-multiply. This is because as you traverse the tree, going from global parts (eg: pelvis) to local parts (eg: elbow, wrist, finger), you don't want your new transformations to take place in "global coordinates", but rather in the (already transformed) coordinate system of the parent part. For example: a person's right knee should bend about the transformed x access at the end of the person's right thigh, not about the global x axis.

To reiterate:

Go back and look at the Matrix.multiply method that you have been using within your translate, rotateX, rotateY, rotateZ and scale methods. You are either doing a pre-multiplication or a post-multiplication. If you want to create a hierarchy of objects, you need to be post-multiplying.

If you're not sure whether you're doing it right, the good news is that there are only two possibilities: if one ordering is wrong, then the other one will work!

Let's take a simple test case, which you can use to test out your matrix multiplication, to see whether you are getting the order right for doing hierarchies. Consider a swinging pendulum with a shaft 10 units long, which swings from a height of 10 units up in y. We can build the pendulum out of two cubes: an elongated one for the pendulum shaft, and a slightly flattened one for the weight at the end.

Let's assume that we have some cube object which, when not transformed, extends from -1.0...+1.0 in x, from -1.0...+1.0 in y and from -1.0...+1.0 in z, and that this cube is drawn by cube.draw().

We can model the pendulum by transforming and then drawing the cube twice: once for the shaft, and then differently for the weight.

   Matrix.translate(m, 0,5,0);     // SLIDE THE SHAFT UPWARDS
   Matrix.scale(m, .1,5,.1);       // SCALE IT TO ELONGATE IN Y
   Matrix.copy(m, cube.matrix); // TRANSFORM THE SHAFT
   cube.draw();                 // DRAW THE SHAFT

   Matrix.scale(m, .5,.5,.1);      // FLATTEN IN Z
   Matrix.copy(m, cube.matrix); // TRANSFORM THE WEIGHT
   cube.draw();                 // DRAW THE WEIGHT
Notice that the code in red only works properly because we have implemented the scale method by using post-multiplication on the results of scaleMatrix. This ensures that the scaling will take place around the middle of the moved shaft, rather than around the global origin.

But how would we swing this cube? If you have implemented push() and pop() methods, as well as methods translate, rotateX, rotateY, rotateZ and scale that always modify mStack[mTop], then this is very straightforward:

   translate(0,10,0);    // SLIDE UP TO TOP OF SHAFT
   push();                  // TRANSFORM AND RENDER THE SHAFT:
      translate(0,-5,0);                      // SLIDE DOWN TO MIDDLE OF SHAFT
      scale(.1,5,.1);                         // SCALE THE SHAFT
      Matrix.copy(mStack[mTop], cube.matrix); // TRANSFORM AND DRAW
   push();                  // TRANSFORM AND RENDER THE WEIGHT
      translate(0,-10,0);                     // SLIDE DOWN TO BOTTOM OF SHAFT
      scale(.5,.5,.1);                        // SCALE THE WEIGHT
      Matrix.copy(mStack[mTop], cube.matrix); // TRANSFORM AND DRAW