Notes on making various shapes

Our goal is to describe various shapes in terms of the two arrays

   int faces[][];
   double vertices[][6];
As we said in class, we will assign a normal vector to each vertex, so each vertex contains not three numeric values but rather six. Eventually we will create the illusion of smooth curved surfaces by using this normal vector data - two adjoining polygonal faces of a shape can be made to appear smooth and rounded by having them share vertices, since they will then share the same normal vector at that vertex.


As we said in class, this means that if we with to create the visual impression of an edge between two adjoining faces, then we must use two different vertices to describe those faces' common point - one vertex having the normal vector each face. So a unit cube, for example, no longer requires eight vertices, but rather twenty four - we cannot reuse the smame vertices where different faces meet at corners of the cube, because those vertices have different surface normals. For example, the four vertices of the back and front faces are, respectively, as follows:


    { -1, -1, -1,   0, 0, -1 }, // FOUR BACK FACE VERTICES
    { -1,  1, -1,   0, 0, -1 }, // IN COUNTERCLOCKWISE ORDER
    {  1,  1, -1,   0, 0, -1 },
    {  1, -1, -1,   0, 0, -1 },


    { -1, -1,  1,   0, 0,  1 }, // FOUR FRONT FACE VERTICES
    {  1, -1,  1,   0, 0,  1 }, // IN COUNTERCLOCKWISE ORDER
    {  1,  1,  1,   0, 0,  1 },
    { -1,  1,  1,   0, 0,  1 },

We follow a similar principle for the left and right faces and for the bottom and top faces.

The face description for the cube now becomes easy:

      {  0,  1,  2,  3},
      {  4,  5,  6,  7},
      {  8,  9, 10, 11},
      { 12, 13, 14, 15},
      { 16, 17, 18, 19},
      { 20, 21, 22, 23},

We make curved shapes by having different faces share vectors, so that they will have the same surface normal at that point. For example, as we discussed in class, to describe an n sided approximation to an open cylindrical tube (where the opening is along the z axis) we need a totwl of 2n vertices. There is a row of back vertices 0..n-1:

   vi = { cos θ , sin θ , -1 , cos θ , sin θ , 0 }
and a row of front vertices n..2n-1:
   vn+i = { cos θ , sin θ , 1 , cos θ , sin θ , 0 }
where θ = (2π i) / n.

We also need n faces, each with four vertices:

   int i1 = (i + 1) % n;
   facei = { i , i1 , n + i1 , n + i }
Given a geometry object
   Geometry g = new Geometry();
you can implement the above logic by defining a method tube(int n) in the Geometry class. Implementing this method consists of filling in a geometry object's faces[] and vertices[] arrays as described above.


To create a sphere as a longitude/latitude globe, we again need to create an array of vertices, and also an array of faces that index into the vertices array. We can create a method in the Geometry class called globe(int m, int n) where m is the number of longitude steps around the equator of our polygon approximation to a sphere, and n is the number of steps from the south pole to the north pole.

Internally we want m × n+1 vertices. We need the extra row of vertices in the n direction because we want to have vertices at both the south pole and the north pole.

There is a little trickiness here because we are thinking of this as a two dimensional array of vertices, but our Geometry data structure for storing vertices is indexed by a single number. We are going to deal with this as follows: First we will act as though we really two have two indices (i,j), and then we will come up with a single index I to describe the index pair (i,j).

As we discussed in class, the location of a single vertex on the globe vi,j in is given by computing the polar coordinates:

   θ = 2π i / m              // LONGITUDE BETWEEN 0 AND 2π
   φ = π j / n - π/2         // LATITUDE BETWEEN -π/2 AND π/2

   x = cos θ cos φ
   y = sin θ cos φ
   z = sin φ

   vi,j = { x , y , z ,   x , y , z }
Note that the normal vector for each vertex is the same as its location. This is only true in the special case where the shape is a unit sphere.

As we said above, we really need to this data into a singly-indexed array of vertices. We do that by defining a single array

   double vertices[ m * (n+1) ][6];
which is indexed by I = i + m * j. For convenience you mght want to define a function within the geometry object that does this mapping:
   double I(int i, int j) { return i + m * j; }
The faces will refer to this single index. The globe will have m × n faces, each with four vertices.
   int faces[][] = new int[m * n][4];
Using our handy dandy index mapping function I(i,j), each face is described as a counterclockwise loop of four vertex indices:
   int i1 = (i + 1) % m;
   faceI(i,j) = { I(i,j) , I(i1,j) , I(i1,j+1) , I(i,j+1) }

A flat circuar disk can be approximated by an n-sided polygon. In this case it is useful to introduce one extra vertex, in the center of the disk, so that you can define the disk as a ring of triangles. So there will be n+1 vertices:

   θ = 2π i / n

   vi = { cos θ , sin θ , 0 ,   0 , 0 , 1 }      where     0 ≤ i < n

   vn = { 0 , 0 , 0 ,   0 , 0 , 1 }
Rather than one giant polygon, it is better to use n triangular faces:
   int i1 = (i + 1) % n;
   facei = { i , i1 , n }

You can make a cylinder by putting together an open tube and two disks as a composite object, using logic something like this:

   Geometry cyl = new Geometry();
   cyl.add().tube(n);                                                                  // THE TUBE
   cyl.add().disk(n).getMatrix().translate(0,0,-1).rotateX(Math.PI);  // END CAP IN BACK
   cyl.add().disk(n).getMatrix().translate(0,0,1);                           // END CAP IN FRONT

Your assignment, due next Wednesday, February 27 (the date was written incorrectly on the first version I posted), is to implement some of these shapes, as many as you can, and do something cool and fun with them. Also, see if you can make other sorts of shapes, like generalized cylinders - extruded shapes with non-circualar cross-section. I guess that would be the Ghostbusters Fun Factory. :-)

If you are ambitious, you might also try a torus. The location of one surface point on a torus is as follows:

    x = cos θ (R + r cos φ)
    y = sin θ (R + r cos φ)
    z = r sin φ
where R is the radius of the large ring, and r is the radius (ie half-thickness) of the tube.