3D Modeling with polygons (Sep 25)

In order to do this week's homework (due Tuesday Oct 2), the following notes from class should be useful.

Please note: At the end of the lecture I was kind of rushing to get through things, and I ended up saying something that was just plain wrong. Sorry about that!

No, you don't put the line drawing routines inside the doubly nested loops. The line drawing part only comes later, when you do the frame-by-frame animation of your transformed shapes.

So please just forget about that confusing bit at the end of the lecture, and use the following notes as your reference. :-)

The basic idea is that you are going to create your shape objects just once, when your program starts, and then render them at each animation frame by transforming each shape and drawing the transformed shape as a set of lines.

You probably will want to create an Java class Shape, that contains a transformation matrix, a vertices[] array, and a faces[] array (see below).

Your Shape object will also contain two temporary arrays int px[] and code py[], to help in drawing pictures of them. I will explain what these are for.


NOTES ON REPRESENTING 3D SHAPES WITH POLYGONS

A shape can be represented as an object that contains:

As we discussed in class, when you want a shape to have a visible edge with a crease, then you should use different vertices on the two sides of the edge (ie: vertices that have the same x,y,z but with different nx,ny,nz), so you can create a discontinuity in surface normal along that edge. But if you want to create the illusion of a continuous curved surface, then you should make two adjacent polygonal faces share the same vertices.

Sphere

Let's say you want to create an approximation of a sphere, where longitude is represented by M steps around the equator and latitude is represented by N steps from south pole to north pole. You can allocate M * (N+1) vertices: M vertices for each of the N+1 latitudes.

For each latitude n, where 0 < n <= N, the location of vertex m is given by:

   θ = 2 * π * m / M
   φ = π * n / N - π/2

   x = cos θ cos φ
   y = sin θ cos φ
   z = sin φ
Notice that to march through the vertex values, you're going to need double nested for loops in order to iterate through all your values of m and all your values of n.

The sphere shape will have M * N rectangular faces.

Torus

The torus is remarkably similar to the sphere. The vertex equations for a torus are given by:
   θ = 2 * π * m / M
   φ = 2 * π * n / N

   x = (a + b * cos θ) * cos φ
   y = (a + b * sin θ) * cos φ
   z = b sin φ
where a and b refer to the major and minor radius, respectively.

Cylinder

To create an approximation of a cylinder, where the circle is represented by N steps (eg: N=8 or N=20) you need to create the central tube and two end caps:

If we let angle theta take on each of the N values around a circle:

θ = 2π i / N   ,   where i = 0, 1, 2, .... N-1
then we can create the central tube with 2N vertices (each with x,y,z and normal nx,ny,nz), where the first N vertices are:
[cosθ, sinθ, -1, cosθ, sinθ, 0]
and the last N vertices are:
[cosθ, sinθ, +1, cosθ, sinθ, 0]
Each of the faces of this central tube will have four sides, so the face[][] array needs to consist of:
0  1  N+1  N
1 2 N+2  N+1
............
N-2 N-1 2N-1 2N-2
N-1 0 N 2N-1
The front cap will contain N+1 vertices, including the added central point [0,0,1, 0,0,1], and its faces will consist of N triangles. The back cap will also contain N+1 vertices, including the added central point [0,0,-1, 0,0,-1], and its faces will also consist of N triangles. I leave it as an exercise for you to create these two end caps. When you've built the complete cylinder, you should have 4N+2 vertices and 3N faces.

Cube

As we discussed in class, a cube is rather simple. You would model the vertices as a unit cube (with extent -1 to +1 in each of the three dimensions), and then rely on the shape's matrix to transform these vertices in order to create all other box-like shapes.

Because we need to separately store vertices that have different normals, there will be 24 vertices; one for each corner of each face of the cube. For example, the cube's four vertices on its leftmost face (the face that points into negative x), are:

-1   -1   -1   -1   0   0  
-1   -1   +1   -1   0   0  
-1   +1   +1   -1   0   0  
-1   +1   -1   -1   0   0  
Notice that I have ordered these four vertices in counterclockwise order, if you are looking at this face of the cube from the outside. I leave it to you to create the vertices for the other five faces of the cube.

The face data for the cube is rather simple, as we discussed in class:

0   1   2   3  
4   5   6   7  
... ... ... ...
20 21 22 23
TRANSFORMING AND RENDERING SHAPES

At every animation frame, for each shape in your scene you'll want to:

  1. animate the shape by manipulating its matrix, as we discussed in class, then

  2. transform each of the shape's vertices by its matrix,

  3. project the transformed point into a pixel coordinate (px,py) on the image (described in more detail below),

  4. display the edges of each of the shape's faces, by drawing a line, using the java.awt.Graphics method drawLine between the (px,py) that corresponds to successive vertices in that face.

In your Shape object you might want to maintain two arrays int px[NV] and int py[NV] into which you can place your transformed and projected vertex data every frame.

How do I project my transformed vertex (x,y,z) onto a pixel (px,py)?

If we were using a real camera with perspective, you'd do something fancier, but for now I'd like you to do something simpler. Suppose your applet window is W pixels wide and H pixels high.

Then all you need to do is:


    px = (int)(W/2 + x*W/2);
    py = (int)(H/2 - y*W/2);

The above operations will ensure that your object will be centered at the middle of the image, and will be about the right size to fit in the image. If you find that your object is turning out really huge, so it's hard to see it in the applet image, then try scaling everything down by using your matrix scale operation.