This assignment is going to focus on two things:
double matrix[][] = new double[4][4];
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:
For example, your method to build a translation matrix might look like this:
identity:
1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 translationMatrix(a,b,c):
1 0 0 a 0 1 0 b 0 0 1 c 0 0 0 1 xRotationMatrix(θ):
1 0 0 0 0 cos(θ) -sin(θ) 0 0 sin(θ) cos(θ) 0 0 0 0 1 yRotationMatrix(θ):
cos(θ) 0 sin(θ) 0 0 1 0 0 -sin(θ) 0 cos(θ) 0 0 0 0 1 zRotationMatrix(θ):
cos(θ) -sin(θ) 0 0 sin(θ) cos(θ) 0 0 0 0 1 0 0 0 0 1 scaleMatrix(a,b,c):
a 0 0 0 0 b 0 0 0 0 c 0 0 0 0 1
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)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.
y' = f y / (f - z)
Finally, you can apply your ViewPort transform to x' and y' to find the pixel location at which to draw your point.