Homework 4, due Monday, Oct 6.

When you have finished the assignment below, post the working applet and the source code onto your class web site.

This assignment is going to focus on two things:

  1. three dimensional transformation matrices, which can be stored in Java as 4×4 arrays:
       double matrix[][] = new double[4][4];
    
  2. parametric surfaces.

Once again, see if you can come up with a cool and interesting and creative picture or animation using instances of shapes (ie: show the same shapes translated, rotated, and scaled in different ways), except that this time your shapes will be 3D parametric surface meshes.

Feel free to use event handling methods to allow the user to interact with your objects in interesting, exciting or deeply moving ways.

3D Transformation Matrices:

Fortunately, 3D matrix primitives are awfully similar to 2D matrix primitives, so implementing the following primitive operations should be easy:

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
For example, your method to build a translation matrix might look like this:
public class Matrix3D {

...

   public static void translationMatrix(double matrix[][], double a, double b, double c) {
      identity(matrix);
      matrix[0][3] = a;
      matrix[1][3] = b;
      matrix[2][3] = c;
   }
}
You'll also want to implement a method to do matrix multiplication. Remember that you do this by doing dot (inner) products between the rows of the first matrix and the columns of the second matrix. Just like in the 2D case, it's convenient to implement a method that copies the contents of one matrix into another. Then to multiply two matrices A and B:
   copy(A,temp); // FIRST COPY TO A TEMPORARY 4×4 MATRIX
   for (int i = 0 ; i < 4 ; i++)
   for (int j = 0 ; j < 4 ; j++)
      A[i][j] = temp[i][0]*B[0][j] + temp[i][1]*B[1][j] + temp[i][2]*B[2][j] + temp[i][3]*B[3][j];
For convenience, you'll probably want to make methods
   translate(matrix, a, b, c);
   rotateX(matrix, theta);
   rotateY(matrix, theta);
   rotateZ(matrix, theta);
   scale(matrix, a, b, c);
which first create the translation, rotation or scale matrix, respectively, and then do a matrix multiply. For example:
   static double tempMatrix[][] = new double[3][3];
   ...
   public static void translate(double matrix[][], double a, double b, double c) {
      translationMatrix(tempMatrix,a,b,c); // CREATES A TRANSLATION MATRIX
      multiply(matrix, tempMatrix);        // MODIFIES THE FIRST ARGUMENT IN PLACE
   }
Finally, you'll want to implement a method that applies a matrix transformation to a point. Here's how I might implement that:
   public static void transform(double src[], double dst[], double matrix[][]) {
      dst[0] = matrix[0][0] * src[0] + matrix[0][1] * src[1] + matrix[0][2] * src[2] + matrix[0][3];
      dst[1] = matrix[1][0] * src[0] + matrix[1][1] * src[1] + matrix[1][2] * src[2] + matrix[1][3];
      dst[2] = matrix[2][0] * src[0] + matrix[2][1] * src[1] + matrix[2][2] * src[2] + matrix[2][3];
   }

This will give you everything you need to combine matrix operations. For example, here is a sequence of steps to first translate, then rotate, and then scale a matrix.

   double[][] m = new double[3][3];
   ...
   Matrix3D.identity(m);
   Matrix3D.translate(m, 1,0,0);
   Matrix3D.rotateY(m, Math.PI/2);
   Matrix3D.scale(m, .5,.5,.5);
If you then use matrix m to transform a point, that point will be translated, rotated and scaled accordingly.

Parametric Surfaces:

The other part of things is the implementation of parametric surfaces. As we discussed in class, you'll want to create a base class ParametricSurface which knows about methods double x(double u, double v), double y(double u, double v) and double z(double u, double v), and which implements a public void draw(double epsilon) method.

The algorithm for the draw method is as follows (in pseudo-code):

   for (v = 0 ; v < 1 ; v += ε)
   for (u = 0 ; u < 1 ; u += ε)
      drawQuad(x(u,v),y(u,v),z(u,v),
               x(u+ε,v),y(u+ε,v),z(u+ε,v),
               x(u+ε,v+ε),y(u+ε,v+ε),z(u+ε,v+ε),
	       x(u,v+ε),y(u,v+ε),z(u,v+ε));

Each instance of ParametricSurface should contain a Matrix3D matrix to transform points before displaying them.

For this assignment you can implement drawQuad just by drawing four vectors on the screen. To do this, drawQuad should first transform each of the four points a, b, c and d by the object's matrix to get a', b', c' and d'. Then apply a ViewPort transformation to each point to get screen locations (Ax,Ay), (Bx,By), (Cx,Cy) and (Dx,Dy). Finally, just call the java.awt.drawLine method four times:

   g.drawLine(Ax,Ay, Bx,By);
   g.drawLine(Bx,By, Cx,Cy);
   g.drawLine(Cx,Cy, Dx,Dy);
   g.drawLine(Dx,Dy, Ax,Ay);

You should at least be able to show that you can display the ParametricSphere example we covered in class:

public class ParametricSphere extends ParametricSurface
{
   public ParametricSphere(double cx, double cy, double cz, double r) {
      // you need to copy these four parameters to internal instance variables
   }

   // This is half in code and half in math.  You'll need to convert it all to code.

   public double x(double u, double v) { θ = 2πu; φ = πv-π/2; return r cos(θ)cos(φ) + cx; }
   public double y(double u, double v) { πv-π/2; return r sin(φ) + cy; }
   public double z(double u, double v) { θ = 2πu; φ = πv-π/2; return -r sin(θ)cos(φ) + cz; }
}

You also might want to implement the parametric Torus (doughnut shape) that we briefly covered in class:

public class ParametricTorus extends ParametricSurface
{
   public ParametricTorus(double R, double r) {
      // you need to copy these parameters to internal instance variables
   }

   // This is half in code and half in math.  You'll need to convert it all to code.

   public double x(double u, double v) { θ = 2πu; φ = 2πv; return cos(θ)(R + r cos(φ)); }
   public double y(double u, double v) { φ = 2πv; return r sin(φ); }
   public double z(double u, double v) { θ = 2πu; φ = 2πv; return -sin(θ)(R + r cos(φ)); }
}

You can also try to make shapes that have other x(), y() and z() methods, if you feel inspired. By applying linear transformations with your shape's matrix, such as non-uniform scale transformations, you can do such things as convert spheres into ellipsoids, which are great for making shapes like fingers and arms and legs, and other stuff like that. You might want to try it. :-)

Perspective:

One final note. I mentioned that for your ViewPort transform you can just throw out the z coordinate. If you're feeling ambitious you can also try implementing perspective, so that objects which are further away appear smaller. It turns that this is really easy to implement:

After you've already done your matrix transform, but before you've done your ViewPort transform, you will have some point (x,y,z). You can simulate a camera which is some distance f away from your object by doing the transformation:

x' = f x / (f - z)
y' = f y / (f - z)
This linear perspective transform will cause objects that are further away (ie: objects with smaller values of z) to appear smaller. I find that a value of f = 10 gives reasonable results for looking at objects that are about one unit wide.

Finally, you can apply your ViewPort transform to x' and y' to find the pixel location at which to draw your point.