Transformation Hierarchy:

In order to create nested transformations, (eg: the limbs of a person, or the steering wheel of a moving automobile), we need a way to create tree structures of transformations. As we discussed in class, you will traverse this tree of nested transformations by using a stack.

NOTE: For this week's assigment I am going to provide you with a very easy way to implement this matrix stack, so that you can practice making hierarchical objects and movements. For this assignment, feel free to use the support code that I provide in these on-line notes. But do not just duplicate the simple one-armed person that I use as an example. You need to come up with your own original example of a model and animation. Creating a full human figure (with all the arms and legs) is ok.

One way to implement the stack is as an array of matrices, together with an integer stack pointer:

   final static int MATRIX_STACK_SIZE = 100;
   int matrixStackTop = 0;
   Matrix matrixStack[] = new Matrix[MATRIX_STACK_SIZE];
   {
      for (int n = 0 ; n < MATRIX_STACK_SIZE ; n++)
         matrixStack[n] = new Matrix();
   }
As we discussed in class, we need a way to reset this stack at the beginning of each animation frame, a way to push/copy a value on the stack, a way to pop off the top value, and a way to get the value of the matrix currently at the top of the stack. This functionality can be implemented by the following four methods:
   // CLEAR THE STACK BEFORE PROCESSING THIS ANIMATION FRAME

   public void mclear() {
      matrixStackTop = 0;
      matrixStack[0].identity();
   }

   // PUSH/COPY THE MATRIX ON TOP OF THE STACK -- RETURN false IF OVERFLOW

   public boolean mpush() {
      if (matrixStackTop + 1 >= MATRIX_STACK_SIZE)
	 return false;

      matrixStack[matrixStackTop + 1].copy(matrixStack[matrixStackTop]);
      matrixStackTop++;
      return true;
   }

   // POP OFF THE MATRIX ON TOP OF THE STACK -- RETURN false IF UNDERFLOW

   public boolean mpop() {
      if (matrixStackTop <= 0)
	 return false;

      --matrixStackTop;
      return true;
   }

   // RETURN THE MATRIX CURRENTLY ON TOP OF THE STACK

   public Matrix m() {
      return matrixStack[matrixStackTop];
   }
As we discussed in class, you can use these methods both for doing modeling operations on your Geometry primitives, and also for doing animation operations.

Generally speaking, you use just the m() method to modify the matrix on top of the stack. You use the mpush() and mpop() methods to traverse the hierarchy that relates the different parts of the model to each other.

For example, here is some simple example code that makes the arm wave on a simple model of a person who has a left arm but no right arm:

   Geometry torso        = addNewGeometry().cylinder(16);
   Geometry head         = addNewGeometry().globe(16, 8);
   Geometry leftUpperArm = addNewGeometry().cylinder(8);
   Geometry leftLowerArm = addNewGeometry().cylinder(8);

   public void update(double time) {

      // CLEAR THE MATRIX STACK FOR THIS ANIMATION FRAME

      mclear();

      // UPDATE THE TORSO SHAPE

      mpush();
         m().rotateX(-Math.PI / 2); // ORIENT THE TORSO CYLINDER UPRIGHT
         m().scale(0.9, 0.7, 1);
         m().translate(0, 0, 1);
         transformAndRender(torso, m());
      mpop();

      mpush();

         // TRANSLATE CURRENT POSITION TO THE NECK JOINT

         m().translate(0, 2, 0);

         // ANY CODE TO NOD OR SHAKE THE HEAD WOULD GO JUST AFTER THIS COMMENT

         // UPDATE THE HEAD SHAPE

         mpush();
            m().translate(0, 1, 0);        // TRANSLATE THE HEAD UP, OFF OF THE TORSO
            m().scale(0.5, 0.7, 0.5);      // SCALE THE HEAD INTO AN ELLIPSOID SHAPE
            transformAndRender(body, m());
         mpop();

      mpop();

      mpush();

         // TRANSLATE CURRENT POSITION TO THE LEFT SHOULDER JOINT

	 m().translate(1, 2, 0);

	 // ROTATE ABOUT THE LEFT SHOULDER JOINT

	 m().rotateZ(Math.PI / 4 * Math.sin(3 * time));

	 // UPDATE THE LEFT UPPER ARM SHAPE

	 mpush();
	    m().rotateY(Math.PI / 2); // UPPER ARM STICKS OUT TO THE RIGHT
	    m().scale(0.2, 0.2, 2);
	    m().translate(0, 0, 1);
            transformAndRender(leftUpperArm, m());
	 mpop();

	 // TRANSLATE CURRENT POSITION TO THE LEFT ELBOW JOINT

	 m().translate(2, 0, 0);

	 // ROTATE ABOUT THE LEFT ELBOW JOINT

	 m().rotateZ(Math.PI / 4 * (1 + Math.sin(3 * time)));

	 // UPDATE THE LEFT LOWER ARM SHAPE

	 mpush();
	    m().rotateY(Math.PI / 2); // LOWER ARM STICKS OUT TO THE RIGHT
	    m().scale(0.2, 0.2, 2);
	    m().translate(0, 0, 1);
            transformAndRender(leftLowerArm, m());
	 mpop();
      mpop();
   }
I leave it up to you to implement the addNewGeometry() and transformAndRender() methods in a way that is consistent with what you've done so far.

YOUR HOMEWORK, DUE BEFORE CLASS STARTS ON THURSDAY MARCH 1:

Create your own animated hierarchical model. It can be anything that's fun and interesting, and that can be built from the shape primitives that you have implemented. Possibilities include a person running, two people dancing. a cat walking, a spider walking, a butterfly in flight, a twisting worm, a pool table with pool balls, an automobile with rolling wheels and a steering wheel, an electric motor, the solar system As you can see, the possibilities are endless.

For this assignment, please incorporate perspective, as described in class and in the on-line notes.