CS 420 Computer Graphics
Lecture Notes
Dr. Tong Yu, Sept. 2005
    1. Introduction
    2. Line Drawing
    3. Drawing Objects
    4. More Drawing Tools
    5. Normal Vectors and Polygonal Models of Surfaces
    6. Viewing I -- Affine Transformations
    7. Viewing II -- Projections
    8. Color
    9. Lighting
    10. Blending, antialiasing, fog ..
    11. Display Lists, Bitmaps and Images
    Appendix. Games and SDL

    Normal Vectors and Polygonal Models of Surfaces

    1. 3D Vectors

    2. Starndard Unit Vectors

        i = ( 1, 0, 0 ),     j = ( 0, 1, 0 ),     k = ( 0, 0, 1 )

    3. Dot Product and Cross Products

        A = ( Ax, Ay, Az ) = Axi + Ayj + Azk

        Magnitude of A

        |A| = ( Ax2 + Ay2 + Az2 )½

        e.g.
        v = ( 2, 4, -1 ) = 2(1, 0, 0 ) + 4(0, 1, 0) - 1( 0, 0, 1 ) = 2i + 4j - k

        |v| = ( 22 + 42 + 12 )½ = ( 21 )½ = 4.5826

        B = ( Bx, By, Bz )

        Dot Product

        A.B = AxBx + AyBy + AzBz = |A||B| cos

        Cross Product

        A x B = ( AyBz - AzBy )i + ( AzBx - AxBz )j + ( AxBy - AyBx )k

        A x B =
        vertical bar
        i
        j
        k
          Ax     Ay     Az  
          Bx     By     Bz  
        vertical bar

        A x B is always perpendicular to A and B

    4. Linear Combination of Vectors

        w = a1v1 + a2v2 + ... + + amvm

      Affine Combination of Vectors

        a1 + a2 + ... + am = 1

        e.g. ( 1 - t )a + ( t ) b

      Convex Combination of Vectors

        a1 + a2 + ... + am = 1     and     ai ≥ 0     for i = 1, ..., m

    5. Basic Properties

      a, b -- scalars, P, Q, R -- 3D vectors

      • P x Q = - ( Q x P )

      • (a P ) x Q = a( P x Q )

      • P x ( Q + R )= P x Q + P x R

      • P x P = 0 = ( 0, 0, 0 )T

      • ( P x Q ) . R = ( R x P ) . Q = ( Q x R ). P

      • P x ( Q x P ) = P x Q x P = P2 Q - ( P . Q )P

      • a ( P + Q ) = a P + a Q

      • ( P + Q ) + R = P + ( Q + R )
    6. Finding the Normal to a Plane

      A normal vector ( or normal for short ) is a vector that points in a direction that is perpendicular to a surface.
      For a plane ( flat surface ), one perpendicular direction is the same for every point on the surface

      Like a vector, a point P can be specified by any three coordinates (i.e. P = ( Px, Py, Pz ). e.g. P = ( 1, 2, 3 )

      A vector field of normals to a surface

      A normal to a surface at a point is the same as a normal to the tangent plane to that surface at that point.

      Any three points P1, P2, P3 determine a unique plane.
      To find the normal to the plane, we build two vectors

        A = P2 - P1
        B = P3 - P1

      The normal is given by
        n = A x B

      ( Note: Looking at the plane ( i.e. front face ), the points P1 P2, P3 appear counter-clockwise )

      Example:

        Find the normal to the plane that passes through the points (1, 0, 2), (2, 3, 0), and (1, 2, 4)

      Solution:

        By direct substitution, A = ( 2, 3, 0 ) - ( 1, 0, 2) = ( 1, 3, -2 ) and B = (1, 2, 4) - (1, 0, 2) = (0, 2, 2), so their cross product n = ( 10, -2, 2 ).

      A normal vector to a plane specified by

        f( x, y, z ) = ax + by + cz + d = 0

      is given by

        N = ( a, b, c ) T

      Class Exercise:

        Find the normal to the plane described by the equation: x + 2y + z - 1 = 0

      General Form:

        For 3-D surface:   F(x, y, z) = 0

        N =
        ∂F
        ∂ x
        ∂ F
        ∂ y
        ∂ F
        ∂ z

      Parametric surface P(s,t):

      N = Ts x Tt

      e.g. x = x ( θ, φ )   y = y ( θ, φ )   z = z ( θ, φ )

      A =
      ∂x
      ∂ θ
      ∂ y
      ∂ θ
      ∂ z
      ∂ θ

      B =
      ∂x
      ∂ φ
      ∂ y
      ∂ φ
      ∂ z
      ∂ φ
      N = A x B

      Why normal vectors are important?

      An object's normal vectors define the orientation of its surface in space -- in particular, its orientation relative to light resources. These vectors are used by OpenGL to determine how much light the object receives at its vertices.

      glNormal*() -- set the normal to value the argument passed in
      subsequent calls to glVertex*() cause the specified vertices to be assigned the current normal

      Example:

      	glBegin( GL_POLYGON );
        	  glNormal3fv( n0 );
        	  glVertex3fv( v0 );
        	  glNormal3fv( n1 );
        	  glVertex3fv( v1 );
        	  glNormal3fv( n2 );
        	  glVertex3fv( v2 );
        	  glNormal3f( 1.0, 1.0, 1.0 );
        	  glVertex3f( 2.0, 3.0, 1.0 );
      	glEnd();  
      	

      A vector that has a length of 1 is said to be normalized

      glEnable( GL_NORMALIZE );

      which prevents scaling from changing the normal lengths.

    7. Vertex Arrays

    8. Function calls are expensive

    9. If use glBegin() .. glEnd(), shared vertices have to be specified again and again.
      Example:
      • A cube has 6 faces, 8 vertices,
      • each face needs 4 vertices to specify
      • will process a total of 24 vertices though 8 would be enough
      Six sides, eight shared vertices

    10. OpenGL has vertex array routines to process arrays of vertices with fewer function calls and make programming more efficient and effective

    11. Three steps in using vertex array:
      1. Enabling Arrays

        void glEnableClientState(GLenum array)
        Specifies the array to enable.
        Acceptable Symbolic constants:
        GL_VERTEX_ARRAY, GL_COLOR_ARRAY, GL_INDEX_ARRAY, GL_NORMAL_ARRAY, GL_TEXTURE_COORD_ARRAY, and GL_EDGE_FLAG_ARRAY

        Example:
        If use lighting, you want to define a surface normal for every vertex. To use vertex arrays for that case, you activate both the surface normal and vertex coordinate arrays:

          glEnableClientState(GL_NORMAL_ARRAY);
          glEnableClientState(GL_VERTEX_ARRAY);

        Can be turned off by:

          glDisableClientState(GL_NORMAL_ARRAY);

      2. Specifying Data for the Arrays

        void glVertexPointer(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer);
        Specifies where spatial coordinate data can be accessed. pointer is the memory address of the first coordinate of the first vertex in the array. type specifies the data type (GL_SHORT, GL_INT, GL_FLOAT, or GL_DOUBLE) of each coordinate in the array. size is the number of coordinates per vertex, which must be 2, 3, or 4. stride is the byte offset between consecutive vertexes. If stride is 0, the vertices are understood to be tightly packed in the array.

        void glColorPointer(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer);

        void glIndexPointer(GLenum type, GLsizei stride, const GLvoid *pointer);

        void glNormalPointer(GLenum type, GLsizei stride, const GLvoid *pointer);

        void glTexCoordPointer(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer);

        void glEdgeFlagPointer(GLsizei stride, const GLvoid *pointer);

        Table Vertex Array Sizes (Values per Vertex) and Data Types

        CommandSizes     Values for type Argument
        glVertexPointer 2, 3, 4     GL_SHORT, GL_INT, GL_FLOAT, GL_DOUBLE
        glNormalPointer3 GL_BYTE, GL_SHORT, GL_INT, GL_FLOAT, GL_DOUBLE
        glColorPointer3, 4 GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT, GL_UNSIGNED_SHORT, GL_INT, GL_UNSIGNED_INT, GL_FLOAT, GL_DOUBLE
        glIndexPointer1 GL_UNSIGNED_BYTE, GL_SHORT, GL_INT, GL_FLOAT, GL_DOUBLE
        glTexCoordPointer1, 2, 3, 4 GL_SHORT, GL_INT, GL_FLOAT, GL_DOUBLE
        glEdgeFlagPointer1 no type argument (type of data must be GLboolean)

        Example:
        Enabling and Loading Vertex Arrays:
        static GLint vertices[] = {25, 25,
                                  100, 325,
                                  175, 25,
                                  175, 325,
                                  250, 25,
                                  325, 325};
        static GLfloat colors[] = {1.0, 0.2, 0.2,
                                  0.2, 0.2, 1.0,
                                  0.8, 1.0, 0.2,
                                  0.75, 0.75, 0.75,
                                  0.35, 0.35, 0.35,
                                  0.5, 0.5, 0.5};
        
        glEnableClientState (GL_COLOR_ARRAY);
        glEnableClientState (GL_VERTEX_ARRAY);
        
        glColorPointer (3, GL_FLOAT, 0, colors);
        glVertexPointer (2, GL_INT, 0, vertices);
        	

        Stride:
        tells OpenGL how to access the data you provide in your pointer value
        its value should be the number of bytes between the starts of two successive pointer elements, or zero, which is a special case

        static GLfloat intertwined[] =
              {1.0, 0.2, 1.0, 100.0, 100.0, 0.0,
               1.0, 0.2, 0.2, 0.0, 200.0, 0.0,
               1.0, 1.0, 0.2, 100.0, 300.0, 0.0,
               0.2, 1.0, 0.2, 200.0, 300.0, 0.0,
               0.2, 1.0, 1.0, 300.0, 200.0, 0.0,
               0.2, 0.2, 1.0, 200.0, 100.0, 0.0};
        	

        Stride allows a vertex array to access its desired data at regular intervals in the array. For example, to reference only the color values in the intertwined array, the following call starts from the beginning of the array (which could also be passed as &intertwined[0]) and jumps ahead 6 * sizeof (GLfloat) bytes, which is the size of both the color and vertex coordinate values. This jump is enough to get to the beginning of the data for the next vertex.

        	
        glColorPointer (3, GL_FLOAT, 6 * sizeof(GLfloat), intertwined);
        	

        For the vertex coordinate pointer, you need to start from further in the array, at the fourth element of intertwined.

        
        	glVertexPointer(3, GL_FLOAT,6*sizeof(GLfloat), &intertwined[3]);
        
        	

      3. Dereferencing and Rendering

        Dereferencing a Single Array Element

          void glArrayElement(GLint ith)
          Obtains the data of one (the ith) vertex for all currently enabled arrays. For the vertex coordinate array, the corresponding command would be glVertex[size][type]v(), where size is one of [2,3,4], and type is one of [s,i,f,d] for GLshort, GLint, GLfloat, and GLdouble respectively. Both size and type were defined by glVertexPointer(). For other enabled arrays, glArrayElement() calls glEdgeFlagv(), glTexCoord[size][type]v(), glColor[size][type]v(), glIndex[type]v(), and glNormal[type]v(). If the vertex coordinate array is enabled, the glVertex*v() routine is executed last, after the execution (if enabled) of up to five corresponding array values.

        Example:
        glEnableClientState (GL_COLOR_ARRAY);
        glEnableClientState (GL_VERTEX_ARRAY);
        glColorPointer (3, GL_FLOAT, 0, colors);
        glVertexPointer (2, GL_INT, 0, vertices);
        
        glBegin(GL_TRIANGLES);
        glArrayElement (2);
        glArrayElement (3);
        glArrayElement (5);
        glEnd();
        

        When executed, the latter five lines of code has the same effect as

        glBegin(GL_TRIANGLES);
        glColor3fv(colors+(2*3*sizeof(GLfloat));
        glVertex2iv(vertices+(2*2*sizeof(GLint));
        glColor3fv(colors+(3*3*sizeof(GLfloat));
        glVertex2iv(vertices+(3*2*sizeof(GLint));
        glColor3fv(colors+(5*3*sizeof(GLfloat));
        glVertex2iv(vertices+(5*2*sizeof(GLint));
        glEnd();
        

        Dereferencing a List of Array Elements

          void glDrawElements(GLenum mode, GLsizei count, GLenum type, void *indices );

          almost has the same effect as:
           
          int i;
          glBegin (mode);
          for (i = 0; i < count; i++)
             glArrayElement(indices[i]);
          glEnd();
          

          mode can be GL_POLYGON, GL_POINTS, ..
           
          Cube with Numbered Vertices

        Example:   Two Ways to Use glDrawElements()

        static GLubyte frontIndices[] = {4, 5, 6, 7};
        static GLubyte rightIndices[] = {1, 2, 6, 5};
        static GLubyte bottomIndices[] = {0, 1, 5, 4};
        static GLubyte backIndices[] = {0, 3, 2, 1};
        static GLubyte leftIndices[] = {0, 4, 7, 3};
        static GLubyte topIndices[] = {2, 3, 7, 6};
        
        glDrawElements(GL_QUADS, 4, GL_UNSIGNED_BYTE, frontIndices);
        glDrawElements(GL_QUADS, 4, GL_UNSIGNED_BYTE, rightIndices);
        glDrawElements(GL_QUADS, 4, GL_UNSIGNED_BYTE, bottomIndices);
        glDrawElements(GL_QUADS, 4, GL_UNSIGNED_BYTE, backIndices);
        glDrawElements(GL_QUADS, 4, GL_UNSIGNED_BYTE, leftIndices);
        glDrawElements(GL_QUADS, 4, GL_UNSIGNED_BYTE, topIndices);
        

        Or better still, crunch all the indices together

        static GLubyte allIndices[] = {4, 5, 6, 7, 1, 2, 6, 5, 
        	0, 1, 5, 4, 0, 3, 2, 1, 
        	0, 4, 7, 3, 2, 3, 7, 6};
        
        glDrawElements(GL_QUADS, 24, GL_UNSIGNED_BYTE, allIndices);
        	

        Note: It is an error to encapsulate glDrawElements() between a glBegin()/glEnd() pair.

          void glDrawArrays(GLenum mode, GLint first, GLsizei count );
        Almost same as
          	glBegin( mode );
          	for ( i = 0; i < count; ++i )
          	  glArrayElement ( first + i );
          	glEnd();
          	

        Interleaved Arrays

        void glInterleavedArrays ( GLenum format, GLsizei stride, void *pointer );

        e.g. glInterleavedArrays( GL_C3F_V3F, 0, interwined );

          This call enables GL_COLOR_ARRAY and GL_VERTEX_ARRAY. It disables other ..ARRAYs like GL_NORMAL_ARRAY, GL_SECONDARY_ARRAY, ..
    12. A Complete Example:

      /*
       *  varray.cpp
       *  This program demonstrates vertex arrays.
       */
      #include <GL/glut.h>
      #include <stdlib.h>
      #include <stdio.h>
      
      #ifdef GL_VERSION_1_1
      #define POINTER 1
      #define INTERLEAVED 2
      
      #define DRAWARRAY 1
      #define ARRAYELEMENT  2
      #define DRAWELEMENTS 3
      #endif
      
      int setupMethod = POINTER;
      int derefMethod = DRAWARRAY;
      
      void setupPointers(void)
      {
         static GLint vertices[] = {25, 25,
                             100, 325,
                             175, 25,
                             175, 325,
                             250, 25,
                             325, 325};
         static GLfloat colors[] = {1.0, 0.2, 0.2,
                             0.2, 0.2, 1.0,
                             0.8, 1.0, 0.2,
                             0.75, 0.75, 0.75,
                             0.35, 0.35, 0.35,
                             0.5, 0.5, 0.5};
      
         glEnableClientState (GL_VERTEX_ARRAY);
         glEnableClientState (GL_COLOR_ARRAY);
      
         glVertexPointer (2, GL_INT, 0, vertices);
         glColorPointer (3, GL_FLOAT, 0, colors);
      }
      
      void setupInterleave(void)
      {
         static GLfloat intertwined[] =
            {1.0, 0.2, 1.0, 100.0, 100.0, 0.0,
             1.0, 0.2, 0.2, 0.0, 200.0, 0.0,
             1.0, 1.0, 0.2, 100.0, 300.0, 0.0,
             0.2, 1.0, 0.2, 200.0, 300.0, 0.0,
             0.2, 1.0, 1.0, 300.0, 200.0, 0.0,
             0.2, 0.2, 1.0, 200.0, 100.0, 0.0};
         
         glInterleavedArrays (GL_C3F_V3F, 0, intertwined);
      }
      
      void init(void) 
      {
         glClearColor (0.0, 0.0, 0.0, 0.0);
         glShadeModel (GL_SMOOTH);
         setupPointers ();
      }
      
      void display(void)
      {
         glClear (GL_COLOR_BUFFER_BIT);
      
         if (derefMethod == DRAWARRAY) 
            glDrawArrays (GL_TRIANGLES, 0, 6);
         else if (derefMethod == ARRAYELEMENT) {
            glBegin (GL_TRIANGLES);
            glArrayElement (2);
            glArrayElement (3);
            glArrayElement (5);
            glEnd ();
         }
         else if (derefMethod == DRAWELEMENTS) {
            GLuint indices[4] = {0, 1, 3, 4};
      
            glDrawElements (GL_POLYGON, 4, GL_UNSIGNED_INT, indices);
         }
         glFlush ();
      }
      
      void reshape (int w, int h)
      {
         glViewport (0, 0, (GLsizei) w, (GLsizei) h);
         glMatrixMode (GL_PROJECTION);
         glLoadIdentity ();
         gluOrtho2D (0.0, (GLdouble) w, 0.0, (GLdouble) h);
      }
      
      void mouse (int button, int state, int x, int y)
      {
         switch (button) {
            case GLUT_LEFT_BUTTON:
               if (state == GLUT_DOWN) {
                  if (setupMethod == POINTER) {
                     setupMethod = INTERLEAVED;
                     setupInterleave();
                  }
                  else if (setupMethod == INTERLEAVED) {
                     setupMethod = POINTER;
                     setupPointers();
                  }
                  glutPostRedisplay();
               }
               break;
            case GLUT_MIDDLE_BUTTON:
            case GLUT_RIGHT_BUTTON:
               if (state == GLUT_DOWN) {
                  if (derefMethod == DRAWARRAY) 
                     derefMethod = ARRAYELEMENT;
                  else if (derefMethod == ARRAYELEMENT) 
                     derefMethod = DRAWELEMENTS;
                  else if (derefMethod == DRAWELEMENTS) 
                     derefMethod = DRAWARRAY;
                  glutPostRedisplay();
               }
               break;
            default:
               break;
         }
      }
      
      void keyboard(unsigned char key, int x, int y)
      {
         switch (key) {
            case 27:
               exit(0);
               break;
         }
      }
      
      int main(int argc, char** argv)
      {
         glutInit(&argc, argv);
         glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB);
         glutInitWindowSize (350, 350); 
         glutInitWindowPosition (100, 100);
         glutCreateWindow (argv[0]);
         init ();
         glutDisplayFunc(display); 
         glutReshapeFunc(reshape);
         glutMouseFunc(mouse);
         glutKeyboardFunc (keyboard);
         glutMainLoop();
         return 0;
      }
      	

    13. Some Hints for Building Polygonal Models of Surfaces

      Any surface can be approximated by polygons.

    14. Keep polygon orientations ( windings ) consistent.

    15. When you subdivide a surface, watch out for any nontriangular polygons. Three points always lie on the same plane.

    16. Pay attention to the trade-off between the display speed and the quality of the image.

    17. For high-quality images, it's better to subdivide more on the outline edges than in the interior.

    18. Try to avoid T-intersections in your models (see Figure below ). As shown, there's no guarantee that the line segments AB and BC lie on exactly the same pixels as the segment AC. Sometimes they do, and sometimes they don't, depending on the transformations and orientation. This can cause cracks to appear intermittently in the surface.

    19. If you're constructing a closed surface, make sure to use exactly the same numbers for coordinates at the beginning and end of a closed loop, or you can get gaps and cracks due to numerical round-off.
      Example of bad code: ( Why ? )

        	const double PI = 3.1415926;
        	const int EDGES = 30;
        
        	//draw a circle
        	glBegin ( GL_LINE_STRIP );
        	for ( int i = 0; i <= EDGES; i++ )
        	  glVertex2f ( cos ( 2*PI*i/EDGES ), sin ( 2*PI*i / EDGES ) );
        	glEnd ();
        	
    20. Example: Building an Icosahedron ( a solid object with 20 faces of equilateral triangles that span 12 vertices )

      #define X .525731112119133606 
      #define Z .850650808352039932
      
      static GLfloat vdata[12][3] = {    
         {-X, 0.0, Z}, {X, 0.0, Z}, {-X, 0.0, -Z}, {X, 0.0, -Z},    
         {0.0, Z, X}, {0.0, Z, -X}, {0.0, -Z, X}, {0.0, -Z, -X},    
         {Z, X, 0.0}, {-Z, X, 0.0}, {Z, -X, 0.0}, {-Z, -X, 0.0} 
      };
      static GLuint tindices[20][3] = { 
         {0,4,1}, {0,9,4}, {9,5,4}, {4,5,8}, {4,8,1},    
         {8,10,1}, {8,3,10}, {5,3,8}, {5,2,3}, {2,7,3},    
         {7,10,3}, {7,6,10}, {7,11,6}, {11,0,6}, {0,1,6}, 
         {6,1,10}, {9,0,11}, {9,11,2}, {9,2,5}, {7,2,11} };
      int i;
      
      /*
      tindices[0][0] = 0, tindices[0][1] = 4, tindices[0][2] = 1
      tindices[1][0] = 0, tindices[1][1] = 9, tindices[1][2] = 4
      ...
      each vertex has 3 coordinates ( that's why ..3fv )
      vdata[[tindices][0][0]][0] = vdata[0][0] = -X
      vdata[[tindices][0][0]][1] = vdata[0][1] = 0.0
      vdata[[tindices][0][0]][2] = vdata[0][2] = Z
      
      */
      glBegin(GL_TRIANGLES);    
      for (i = 0; i < 20; i++) {    
         /* color information here */
        //...... 
         //vertex information below
         glVertex3fv(&vdata[tindices[i][0]][0]); 
         glVertex3fv(&vdata[tindices[i][1]][0]); 
         glVertex3fv(&vdata[tindices[i][2]][0]); 
      }
      glEnd();
      
        
      Example: A Twenty-sided die
    21. The strange numbers X and Z are chosen so that the distance from the origin to any of the vertices of the icosahedron is 1.0. ( note: X2 + Z2 = 1 )
    22. tindices[] tells how to link the vertices to make triangles
    23. Need to calculate normals for surfaces if they are to be lit.

      //normalize a vector
      void normalize(float v[3]) {    
         GLfloat d = sqrt(v[0]*v[0]+v[1]*v[1]+v[2]*v[2]); 
         if (d == 0.0) {
            printf("\nErrot: zero length vector");    
            return;
         }
         v[0] /= d; v[1] /= d; v[2] /= d; 
      }
      
      //v1[], v2[] are two vectors
      //out[] holds the crossproduct v1 x v2 
      void normcrossprod(float v1[3], float v2[3], float out[3]) 
      { 
         GLint i, j; 
         GLfloat length;
      
         out[0] = v1[1]*v2[2] - v1[2]*v2[1]; 
         out[1] = v1[2]*v2[0] - v1[0]*v2[2]; 
         out[2] = v1[0]*v2[1] - v1[1]*v2[0]; 
         normalize(out); 
      }
        

      /*
        Note: difference between two points is a vector
        i.e. 	A = P0 - P1
       	B = P2 -  p1
      	A x B is normal to the plane passing through points P0, P1, P2,
      	and P0, P1, P2 appear counter-clockwise
        ( see notes above )
      */
      GLfloat d1[3], d2[3], norm[3];    
      for (j = 0; j < 3; j++) {    
         d1[j] = vdata[tindices[i][0]][j] - vdata[tindices[i][1]][j];    
         d2[j] = vdata[tindices[i][2]][j] - vdata[tindices[i][1]][j];    
      }
      normcrossprod(d1, d2, norm); 
      glNormal3fv(norm);
        

      If you're using an icosahedron as an approximation for a shaded sphere, you'll want to use normal vectors that are perpendicular to the true surface of the sphere, rather than being perpendicular to the faces. For a sphere, the normal vectors are simple; each points in the same direction as the vector from the origin to the corresponding vertex. Since the icosahedron vertex data is for an icosahedron of radius 1, the normal and vertex data are identical.

      glBegin(GL_TRIANGLES); 
      for (i = 0; i < 20; i++) {    
            glNormal3fv(&vdata[tindices[i][0]][0]); 
            glVertex3fv(&vdata[tindices[i][0]][0]); 
            glNormal3fv(&vdata[tindices[i][1]][0]); 
            glVertex3fv(&vdata[tindices[i][1]][0]); 
            glNormal3fv(&vdata[tindices[i][2]][0]); 
            glVertex3fv(&vdata[tindices[i][2]][0]); 
      }
      glEnd();
        

      Improving the Model

      Recursive Subdivision

      Generalized Subdivision

      consider an arbitrary surface parameterized by two variables u[0] and u[1]. Suppose that two routines are provided:

      void surf(GLfloat u[2], GLfloat vertex[3], GLfloat normal[3]); 
      float curv(GLfloat u[2]);
        

      If surf() is passed u[], the corresponding three-dimensional vertex and normal vectors (of length 1) are returned. If u[] is passed to curv(), the curvature of the surface at that point is calculated and returned.

      The following shows the recursive routine that subdivides a triangle either until the maximum depth is reached or until the maximum curvature at the three vertices is less than some cutoff.

      
       void subdivide(float u1[2], float u2[2], float u3[2],
                      float cutoff, long depth)
      {
         GLfloat v1[3], v2[3], v3[3], n1[3], n2[3], n3[3];
         GLfloat u12[2], u23[2], u32[2];
         GLint i;
      
         if (depth == maxdepth || (curv(u1) < cutoff &&
      	curv(u2) < cutoff && curv(u3) < cutoff)) {
            surf(u1, v1, n1); surf(u2, v2, n2); surf(u3, v3, n3);
            glBegin(GL_POLYGON);
               glNormal3fv(n1); glVertex3fv(v1);
               glNormal3fv(n2); glVertex3fv(v2);
               glNormal3fv(n3); glVertex3fv(v3);
            glEnd();
            return;
         }
         for (i = 0; i < 2; i++) {
            u12[i] = (u1[i] + u2[i])/2.0;
            u23[i] = (u2[i] + u3[i])/2.0;
            u31[i] = (u3[i] + u1[i])/2.0;
         }
         subdivide(u1, u12, u31, cutoff, depth+1);
         subdivide(u2, u23, u12, cutoff, depth+1);
         subdivide(u3, u31, u23, cutoff, depth+1);
         subdivide(u12, u23, u31, cutoff, depth+1);
      }
        

    24. Solid Modeling with Polygonal Meshes

    25. Polygon mesh -- collection of polygons
    26. Vertex vs. Face Normals
      • Smooth surfaces -- use vertex normal ( e.g. cylinder, sphere )
      • Flat surfaces -- use face normals
    27. Defining a Polygon Mesh
      • Use three separate lists, vertex list, normal list, face list
      e.g. a barn
        Normal nx ny nz
        0 -1 0 0
        1 -0.7071 0.7071 0
        2 0.7071 0.7071 0
        3 1 0 0
        4 0 -1 0
        5 0 0 1
        6 0 0 -1
         
        Face Vertices Associated
        Normal
        0 (left) 0,5,9,4 0,0,0,0
        1 (roof left) 3, 4, 9, 8 1, 1, 1, 1
        2 (roof right) 2, 3, 8, 7 2, 2, 2, 2
        3 (right) 1, 2, 7, 6 3, 3, 3, 3
        4 (bottom) 0, 1, 6, 5 4, 4, 4, 4
        5 (front) 5, 6, 7, 8, 9 5, 5, 5, 5, 5
        6 (back) 0, 4, 3, 2, 1 6, 6, 6, 6, 6

      Implementation

        class Point3{
        public:
                float x,y,z;
        
                void set(float dx, float dy, float dz);
                void set(Point3& p);
                Point3(float xx, float yy, float zz);
                Point3();
        
        };
        
        class VertexID {
        public: 
          int vertIndex;	//index of this vertex in the vertex list
          int normIndex;	//index of this vertex's normal
        };
        //used to define a Mesh
        class Face{
        public:
          int nVerts;
          VertexID * vert; 	//array of vertex and normal indices
        
          Face();	   	//constructor
          ~Face();		//destructor
        };
        
        class Mesh {
        private:
          int numVerts, numNorms, numFaces;
          Point3 *pt; 		// array of points ( 3D vertices
          Vector3 *norm; 	// array of normals
          Face *face; 		// array of faces
          int lastVertUsed;
          int lastNormUsed;
          int lastFaceUsed;
        
        public:
          string meshFileName; 	// holds file name for this Mesh
        
          void readMesh(string fname);
          void writeMesh(char* fname);
          void printMesh();
          void drawMesh();
          void drawEdges();
          void freeMesh();
          int isEmpty();
          void makeEmpty();
          Mesh();
          virtual void drawOpenGL();
          Mesh(string fname);
          Vector3 newell4(int indx[]);
          bool hit(Ray &r, Intersection &inter);
        }; // end of Mesh class
        
        
        void Mesh::drawMesh()   // use OpenGL to draw this mesh
        {
          // draw each face of this mesh using OpenGL: draw each polygon.
          if( isEmpty() ) return; // mesh is empty
        
          for(int f = 0; f < numFaces; f++) // draw each face
          {
            glBegin(GL_POLYGON);
            cout << endl;
            setColor( f );
            for(int v = 0; v < face[f].nVerts; v++) // for each vertex
            {
                int in = face[f].vert[v].normIndex ; // index of this normal
                int iv =  face[f].vert[v].vertIndex ; // index of this vertex
                glNormal3f(norm[in].x, norm[in].y, norm[in].z);
                glVertex3f(pt[iv].x, pt[iv].y, pt[iv].z);
            }
            glEnd();
            cout << endl;
          }
        } //drawMesh
        
    28. Tweening for Art and Animation

      Polyline Q formed by points: Q0, Q1, .., Qi, ... Qn-1

      Polyline R formed by points: R0, R1, .., Ri, ... Rn-1

      We construct polyline P with points: P0, P1, .., Pi, ... Pn-1 by 'interpolating' Q and R, i.e.

        Pi(t) = ( 1 - t ) Qi + tRi

        When   t = 0, Pi = Qi ( i.e. P = Q )

        When   t = 1, Pi = Ri ( i.e. P = R )

      So when t changes from 0, to 0.1, 0.2, ..., to 1, P begins with the shape Q and gradaully morphs to R.
      Point2 tween( Point2 P1, Point2 P2, float t )
      {
        Point2 P;
        P.x = P1.x + ( P2.x - P1.x ) * t;
        P.y = P1.y + ( P2.y - P1.y ) * t;
        return P;
      }
      
      void drawTween(Point2 A[], Point2 B[], int n, float t, Point2 c)
      {   // draw the tween at time t between polylines A and B
        Point2 P0 = tween(A[0], B[0],t);
        cvs.moveTo(P0.x+c.x, P0.y+c.y);
      
        for(int i = 1; i < n; i++)
        {
          Point2 P;
          P = tween(A[i], B[i],t);
          cvs.lineTo(P.x+c.x, P.y+c.y);
        }
        cvs.lineTo(P0.x+c.x, P0.y+c.y);
      }
              
      man ←→ woman