Homework 6, due Wednesday, Oct 16.

This week we're going to make a motion machine!

Your assignment is to write a java applet that makes some sort of interactively animated person, creature, machine, dancing tree, hopping vehicle, or whatever. The only rules are:

  1. You need to implement a kinematic machine whose API is as described below.

  2. You need to build your content on top of this kinematic machine.

As usual, there are plenty of opportunities for extra credit. The more ambitious you are, the more extra credit you'll get. People who knock my socks off can get a score of 15. Here are just a few ideas among many; some are easy, some are hard:

API for a kinematic motion machine:

A kinematic motion library can be implemented as an object class KM. Here is the minimum set of public methods you need to implement:

   void initFrame();
   void push();
   void pop();
   void translate(double x,double y,double z);
   void rotateX(double theta);
   void rotateY(double theta);
   void rotateZ(double theta);
   void scale(double x,double y,double z);
   void transform(double src[], double dst[])
You should call km.initFrame() at the start of every frame. This is the method that resets the kinematic machine's internal matrix stack S[] to contain just a single identity matrix, so that sp=0 and S[sp].identity().

Then you call the other routines in any sequence you want, with the the caveat that calls to km.push() and km.pop() must occur in matching pairs (like the rules for parentheses).

Remember that the translate, rotate and scale methods operate only on the top item in the matrix stack S[sp].

The km.push() method duplicates the top entry in the stack, thereby making the stack grow.

The km.pop() method just decrements the stack pointer.

When you implement km.push() and km.pop(), be sure to put in code that checks for stack overflow and underflow errors.

The method km.transform(src,dst) allows you to query the kinematic machine so that you can draw things. For example, if you want to draw the square:

   double A[][] = {{0,0,0},{.1,0,0},{.1,.1,0},{0,.1,0}};
you might end up calling
   drawPolygon(double A);
where you might, for example, implement drawPolygon() as in the code that follows.

Below is a really simple example of an applet that uses the API that I described above. It just draws two cubes, an inner cube spinning inside an outer cube. Don't hand this in as your homework, but go ahead and use it for reference:

import java.awt.*;

public class testKM extends GenericApplet
{
   int width, height, x = 100, y = 100;
   KM km = new KM();                    // ALLOCATE THE KINEMATIC MACHINE

   // DEFINE 8 VERTICES AND 6 FACES OF A CUBE

   double aaa[] = {-1,-1,-1}, baa[] = { 1,-1,-1}, aba[] = {-1, 1,-1}, bba[] = { 1, 1,-1},
          aab[] = {-1,-1, 1}, bab[] = { 1,-1, 1}, abb[] = {-1, 1, 1}, bbb[] = { 1, 1, 1};
   double a[][][] = {
      { aaa, baa, bba, aba }, { aab, bab, bbb, abb }, { aaa, aba, abb, aab },
      { baa, bba, bbb, bab }, { aaa, baa, bab, aab }, { aba, bba, bbb, abb },
   };

   double L[][] = new double[100][3];
   int count = 0;

   public void render(Graphics g) {
      if (width == 0) {
         width  = bounds().width;
         height = bounds().height;
      }
      if (damage) {
         g.setColor(Color.white);
         g.fillRect(0, 0, width, height);
         g.setColor(Color.black);

	 km.initFrame();           // INIT FRAME
	 km.scale(.2,.2,.2);
	 km.rotateY(.1 * x);       // RESPOND TO MOUSE
	 km.rotateX(.1 * y);

	 // DRAW THE OUTER CUBE

         for (int n = 0 ; n < a.length; n++)
            drawPolygon(g, a[n]);

	 km.scale(.5,.5,.5);       // SCALE DOWN
	 km.rotateX(.11 * count);
	 km.rotateY(.12 * count);  // SPIN
	 km.rotateZ(.13 * count);

	 // DRAW THE INNER CUBE

         for (int n = 0 ; n < a.length; n++)
            drawPolygon(g, a[n]);

         count++;
      }
   }

   void drawPolygon(Graphics g, double A[][]) {

      // DECLARE A TEMPORARY ARRAY

      double B[][] = new double[A.length][3];

      // TRANSFORM ALL VERTICES OF POLYGON INTO TEMPORARY ARRAY

      for (int i = 0 ; i < A.length ; i++)
         km.transform(A[i], B[i]);

      // DRAW LINES BETWEEN SUCCESSIVE TRANSFORMED VERTICES

      for (int i = 0 ; i < A.length ; i++) {
         int j = (i + 1) % A.length;
         g.drawLine(viewportX(B[i]),viewportY(B[i]),
                    viewportX(B[j]),viewportY(B[j]));
      }
   }

   double F = 3.5;  // FOCAL LENGTH

   int viewportX(double V[]) {
      // HERE'S WHERE YOU MIGHT IMPLEMENT PERSPECTIVE
      return (int)(width/2 + V[0] * width);
   }

   int viewportY(double V[]) {
      // HERE'S WHERE YOU MIGHT IMPLEMENT PERSPECTIVE
      return (int)(height/2 - V[1] * width);
   }

   public boolean mouseDown(Event e, int x, int y) { moveXY(x, y); return true; }
   public boolean mouseDrag(Event e, int x, int y) { moveXY(x, y); return true; }
   void moveXY(int x, int y) { this.x = x; this.y = y; damage = true; }
}