Exercise 2: Shape Primitives

This exercise will provide an introduction to the various 2D and 3D shape primitives used in OpenGL and GLUT.



Preliminaries

Please create a directory ~/cgi/ex2 and copy /vol/www/ee/Teaching/Courses/CGI/exercise2/*  to  ~/cgi/ex2. You should now be ready to start.



1. Primitives: Point/Line/Triangle

(i) Open the file 'draw.c' which is a basic program for drawing 2D shapes
         - the program is setup to draw a three points of size of 1 pixel

(ii) Compile and run the program
       make -f make_draw
       ./draw
      You should see a window containing three points

(iii) Modify the program to draw a line between pairs of points given by GL_LINES
      the line width can be modified with glLineWidth()

(iv) Next draw a closed loop of lines using GL_LINE_LOOP
     - this should show the outline of a triangle

(v) Now modify the program to draw a single coloured triangle using GL_TRIANGLES

(vi) Add one additional vertices to draw two triangles using GL_TRIANGLE_STRIP
       glVertex2f(0.5,0.5);

(vii) Next draw the same polgygon using GL_QUADS and GL_POLYGON

(vii) Change the colour of each of the vertices so that they are different for each vertex   using
       glColor3f(r,g,b) to specify individual colours for vertices

All shapes in OpenGL are drawn using the primitives (point/line/triangle) covered in this section.



2. Vertex Arrays

The primitives shapes in the previous section were created by specifiying individual vertices
between the glBegin() and glEnd() statements. This is inefficient for large numbers of polygons as it requires one function call glVertex2f() for each vertex plus an addition function call for each vertex
property glColor3f() etc. The use of repeated function calls can dominate drawing time for large shapes.  OpenGL has a mechanism called 'vertex arrays' which allow specification of vertices and
related data such as colour in arrays which can be accessed directly
with a small number of function calls.  In this exercise you will modify the primitive drawing program
to use vertex arrays.

Three modifications are required to use vertex arrays:
(i) Enable the use of a vertex array by adding the following command in the GL initialisation:
    glEnableClientState(GL_VERTEX_ARRAY);

(ii) Specify data for the vertex array by declaring  a static array of vertices at the start of the program
    static GLfloat vertices[]= {-0.5,-0.5,
                                              0.5,-0.5,
                                                0.0,0.5,
                                               0.5,0.5};
(iii) Next, specify how the data is stored in the array using the command
     glVertexPointer(2,GL_FLOAT,0,vertices); in the display function.

    The vertex pointer function is specified as follows:
    glVertexPointer(GLint size,GLenum type,GLsizei stride,GLvoid *pointer);
         size - number of coordiantes for each vertex
         type - specifies the data type
         stride - byte offset between consecutive vertices
         pointer - pointer to the vertex array

(iv) To draw a triangle using the vertex array there are a number of methods
      Try implementing each method to verify that they produce the same result.

       Method 1: Dereference a single array element
          To draw a single triangle:
            glBegin(GL_TRIANGLES);
                glArrayElement(0);
                glArrayElement(1);
                glArrayElement(2);
            glEnd();

       Note: when the first array element is dereferenced the entire array is sent from the client to the
       server for rendering.

       Method 2: Dereference a list of array element
        This method uses a second array of indicies to define one or more primitives:
       At the start of the program define a static array
            static  GLubyte triangle1[] = {0,1,2};
       In the display function
           glDrawElements(GL_TRIANGLES,3,GL_UNSIGNED_BYTE,triangle1);

        Method 3: Dereference a sequence of array elements
        This functions constucts the entire sequence of primitives from all enabled arrays

            glDrawArrays(GL_TRIANGLE_STRIP,0,4);

(v) Now create a second array to store the vertex colours and enable vertex colouring following steps
     (i)-(iii) above. Use functions:
            glEnableClientState(GL_COLOR_ARRAY);
            glColorPointer(....);

    You should now be able to draw the coloured triangles with methods 1-3 above. Note using
    the vertex arrays you do not require function calls to glVertex*() & glColor*() for each vertex.



3.. 3D Shape Primitives

GLUT provides several routines for drawing common 3D shapes.

(i) Modify the draw.c program to draw a cube using the following function:
        glutWireCube(0.5);

    The cube is created at the origin with aligned with the axis.
    Rotate the cube so that you can see more than one side.
        glRotatef(45,0.0,1.0,1.0);

(ii) Draw a solid shaded version of the cube using the command
       glutSolidCube(0.5);

    Note the edges are not visible as there is no scene lighting.

(iii) Other 3D shape primitives can be drawn using the GLUT functions:
      glutWireSphere(0.5,10,10);  - the parameters 10,10 are horizontal/vertical slices.
      glutWireTorus(0.5,0.75,10,10); - the first 2 parameters specify inner and outer radius
      glutWireIcosahedron();
      glutWireOctahedron();
      glutWireTetrahedron();
      glutWireDodecahedron();
      glutWireCone(0.5,0.75,10,10);
      glutWireTeapot(0.5); - the famous Utah teapot

     Create a scene with multiple 3D shapes of different colours.
     Shapes can be rotated and translated from the origin using the functions:
       glRotatef(angle, x,y,z);  - rotate by angle about axis (x,y,z)
       glTranslatef(x,y,z);  - translate by (x,y,z)

    Remember to reset the model transformation state to identity between each shape otherwise the
    translation and rotation are cummulative. Use:
       glLoadIdentity();



4. Building a Sphere

This exercise illustrates the representation of 3D shapes using simple primitives such as triangles.
A sphere can be approximated to arbitrarily well using smaller and smaller triangles.

(i) Open the program 'draw_sphere.c'.
    This program models a sphere of unit radius centered at the origin by triangulating the surface.
    Starting with a tetrahedra with 4 vertices on the unit sphere each triangular face of the
    tetrahedra is subdivided into 4 triangles.  The triangle is subdivided by computing the mid-point
    of each edge.

    Take a look at the function subdivide to ensure that you understand how it works.

    Compile and run the program with 'nlevel=1':
    make -f make_draw_sphere
    ./draw_sphere

    Try changing the number of levels 'nlevel' to 0,2,4,6 to see the change in shape.

(ii) To create the tesellation of a sphere (centered on the origin) we can normalise the distance
     of vertices from the origin be one unit. The normalised vertices will then lie on the unit sphere.

    Add a function normalise() to normalise the vertices to unit length by dividing by the
    distance of the vertex from the origin. In other words, the vector   length = sqrt(x*x + y*y +z*z).

    Implement the normalise routine, normalise the vertex lengths in the subdivision routine
    then recompile and run the program.

    Try changing the number of levels 'nlevel' to 0,2,4,6 to see the change in shape.
 

(iii) Now, change the program to inteactively increase/decrease the number of levels
      by clicking on the right/left mouse buttons (see exercise 1.4). Use the GLUT functions
      to process the mouse input, change nlevel and

(iv) Viewing the wireframe model it is difficult to distinguish triangles for the front and back of the
      model. Modify the program to not display triangles facing away from the triangle by enabling
     back face culling in initgl():

      glEnable(GL_CULL_FACE);
      glCullFace(GL_BACK);